Liferay’s API based approaches to overriding JSPs (i.e., Dynamic Includes and Portlet Filters) are the best way to override JSPs in apps and in the core. You can also use Custom JSP Bags to override core JSPs. But the approach is not as stable as the API based approaches. If your Custom JSP Bag’s JSP is buggy (because of your code or because of a change in Liferay), you are most likely to find out at runtime, where functionality breaks and nasty log errors greet you. Using Custom JSP Bags to override JSPs is a bad practice, equivalent to using Ext plugins to customize Liferay DXP. If you’re maintaining existing Custom JSP Bags, however, this tutorial explains how they work.
A Custom JSP Bag module must satisfy these criteria:
-
Provides and specifies a custom JSP for the JSP you’re extending.
-
Includes a
CustomJspBag
implementation for serving the custom JSPs.
The module provides transportation for this code into Liferay’s OSGi runtime. After you create your new module, continue with providing your custom JSP.
Providing a Custom JSP
Create your JSPs to override Liferay DXP core JSPs. If you’re using the Maven
Standard Directory Layout,
place your JSPs under src/main/resources/META-INF/jsps
. For example, if you’re
overriding
portal-web/docroot/html/common/themes/bottom-ext.jsp
place your custom JSP at
[your module]/src/main/resources/META-INF/jsps/html/common/themes/bottom-ext.jsp
Implement a Custom JSP Bag
Liferay DXP (specifically the
CustomJspBagRegistryUtil
class)
loads JSPs from
CustomJspBag
services. The following steps implement a custom JSP bag.
-
In your module, create a class that implements
CustomJspBag
. -
Register your class as an OSGi service by adding an
@Component
annotation to it, like this:@Component( immediate = true, property = { "context.id=BladeCustomJspBag", "context.name=Test Custom JSP Bag", "service.ranking:Integer=100" } )
immediate = true
: Makes the service available on module activation.context.id
: Your custom JSP bag class name. ReplaceBladeCustomJspBag
with your class name.context.name
: A more human readable name for your service. Replace it with a name of your own.service.ranking:Integer
: A priority for your implementation. The container chooses the implementation with the highest priority.
-
Implement the
getCustomJspDir
method to return the folder path in your module’s JAR where the JSPs reside (for example,META-INF/jsps
).@Override public String getCustomJspDir() { return "META-INF/jsps/"; }
-
Create an
activate
method and the following fields. The method adds the URL paths of all your custom JSPs to a list when the module is activated.@Activate protected void activate(BundleContext bundleContext) { _bundle = bundleContext.getBundle(); _customJsps = new ArrayList<>(); Enumeration<URL> entries = _bundle.findEntries( getCustomJspDir(), "*.jsp", true); while (entries.hasMoreElements()) { URL url = entries.nextElement(); _customJsps.add(url.getPath()); } } private Bundle _bundle; private List<String> _customJsps;
-
Implement the
getCustomJsps
method to return the list of this module’s custom JSP URL paths.@Override public List<String> getCustomJsps() { return _customJsps; }
-
Implement the
getURLContainer
method to return a newcom.liferay.portal.kernel.url.URLContainer
. Instantiate the URL container and override itsgetResources
andgetResource
methods. ThegetResources
method looks up all the paths to resources in the container by a given path. It returns aHashSet
ofStrings
for the matching custom JSP paths. ThegetResource
method returns one specific resource by its name (the path included).@Override public URLContainer getURLContainer() { return _urlContainer; } private final URLContainer _urlContainer = new URLContainer() { @Override public URL getResource(String name) { return _bundle.getEntry(name); } @Override public Set<String> getResources(String path) { Set<String> paths = new HashSet<>(); for (String entry : _customJsps) { if (entry.startsWith(path)) { paths.add(entry); } } return paths; } };
-
Implement the
isCustomJspGlobal
method to returntrue
.@Override public boolean isCustomJspGlobal() { return true; }
Now your module provides custom JSPs and a custom JSP bag implementation. When you deploy it, Liferay DXP uses its custom JSPs in place of the core JSPs they override.
Extend a JSP
If you want to add something to a core JSP, see if it has an empty -ext.jsp
and override that instead of the whole JSP. It keeps things simpler and more
stable, since the full JSP might change significantly, breaking your
customization in the process. By overriding the -ext.jsp
, you’re only relying
on the original JSP including the -ext.jsp
. For an example, open
portal-web/docroot/html/common/themes/bottom.jsp
, and scroll to the end.
You’ll see this:
<liferay-util:include page="/html/common/themes/bottom-ext.jsp" />
If you must add something to bottom.jsp
, override bottom-ext.jsp
.
Since Liferay DXP 7.0, the content from the following JSP files formerly in
html/common/themes
are inlined to improve performance.
body_bottom-ext.jsp
body_top-ext.jsp
bottom-ext.jsp
bottom-test.jsp
They’re no longer explicit files in the code base. But you can still create them in your module to add functionality and content.
Remember, this type of customization is a last resort. Your override may break due to the nature of this implementation, and core functionality in Liferay can go down with it. If the JSP you want to override is in another module, refer to the section on Liferay API based approaches to overriding JSPs.
Site Scoped JSP Customization
In Liferay Portal 6.2, you could use Application Adapters to scope your core JSP customizations to a specific Site. Since the majority of JSPs were moved into modules for Liferay DXP 7.0, the use case for this has shrunk considerably. If you must scope a core JSP customization to a Site, prepare an application adapter as you would have for Liferay Portal 6.2, and deploy it to Liferay DXP 7.1. It will still work. However, note that this approach is deprecated in Liferay DXP 7.1 and won’t be supported at all in Liferay 8.0.