Overriding JSPs
You can override JSPs completely using OSGi fragments. This approach is powerful but can make things unstable when the host module is upgraded.
-
By overriding an entire JSP, you might not account for new content or new widgets essential to new host module versions.
-
Fragments are tied to a specific host module version. If the host module is upgraded, the fragment detaches from it. In this scenario, the original JSPs are still available and the module is functional (but lacks your JSP enhancements).
-
Liferay cannot guarantee that JSPs overridden by fragments can be upgraded.
Using OSGi fragments to override JSPs should only be done as a last resort. Dynamic Includes and Portlet Filters provide more stability as they customize specific parts of JSPs that are safe to override and don’t limit your override to a specific host module version.
An OSGi fragment that overrides a JSP requires these two things:
-
The host module’s symbolic name and version in the OSGi header
Fragment-Host
declaration. -
The modified JSP built on top of a default JSP in Liferay.
For more information about fragment modules, see the OSGi Alliance’s documentation.
Provide the Overridden JSP
There are two possible naming conventions for targeting the host original JSP: portal
or original
. For example, if the original JSP is in the folder /META-INF/resources/login.jsp
, the fragment bundle should contain a JSP with the same path, using the following pattern:
<liferay-util:include
page="/login.original.jsp" (or login.portal.jsp)
servletContext="<%= application %>"
/>
Then create your modifications, making sure you mimic the host module’s folder structure when overriding its JAR. If you’re overriding the login application’s login.jsp
for example, you’d put your own login.jsp
in
my-jsp-fragment/src/main/resources/META-INF/resources/login.jsp
If you must post-process the output, you can update the pattern to include Liferay DXP’s buffering mechanism. Below is an example that overrides the original create_account.jsp
:
<%@ include file="/init.jsp" %>
<liferay-util:buffer var="html">
<liferay-util:include page="/create_account.portal.jsp"
servletContext="<%= application %>"/>
</liferay-util:buffer>
<liferay-util:buffer var="openIdFieldHtml"><aui:input name="openId"
type="hidden" value="<%= ParamUtil.getString(request, "openId") %>" />
</liferay-util:buffer>
<liferay-util:buffer var="userNameFieldsHtml"><liferay-ui:user-name-fields />
</liferay-util:buffer>
<liferay-util:buffer var="errorMessageHtml">
<liferay-ui:error
exception="<%= com.liferay.portal.kernel.exception.NoSuchOrganizationException.class %>" message="no-such-registrationcode" />
</liferay-util:buffer>
<liferay-util:buffer var="registrationCodeFieldHtml">
<aui:input name="registrationCode" type="text" value="">
<aui:validator name="required" />
</aui:input>
</liferay-util:buffer>
<%
html = com.liferay.portal.kernel.util.StringUtil.replace(html,
openIdFieldHtml, openIdFieldHtml + errorMessageHtml);
html = com.liferay.portal.kernel.util.StringUtil.replace(html,
userNameFieldsHtml, userNameFieldsHtml + registrationCodeFieldHtml);
%>
<%=html %>
Declaring a Fragment Host
Your custom fragment is rendered as part of the chosen host module. To choose a host, you must declare two things in your module’s bnd.bnd
:
-
The Bundle Symbolic Name of the host module. This is the module containing the original JSP.
-
The exact version of the host module to which the fragment belongs.
Both are declared together using the OSGi manifest header Fragment-Host
:
Fragment-Host: com.liferay.login.web;bundle-version="[1.0.0,1.0.1)"
Supplying a specific host module version is important. If that version of the module isn’t present, your fragment won’t attach itself to a host. This is by design as a new version of the host module might have changed its JSPs. If your now-incompatible version of the JSP could be applied to the host module, it would break the functionality of the host.
Using Fragment Host Internal Packages
To use an internal (un-exported) host package, the fragment must explicitly exclude the package from its Import-Package:
manifest header. For example, this Import-Package
header excludes packages that match com.liferay.portal.search.web.internal.*
.
Import-Package: !com.liferay.portal.search.web.internal.*,*
By default, the OSGi module adds the package to the Import-Package:
header, so you must manually exclude it. Attempting to start the fragment while requiring an un-exported package fails because the package is an unresolved requirement. Each fragment has full access to the host packages, including its internal (un-exported) packages already.