() {
@Override
public void performAction(Bar bar) {
int field3 = bar.getField3();
field3++;
bar.setField3(field3);
_barLocalService.updateBar(bar);
}
});
\end{verbatim}
\item
Execute the action on each matching entity.
\begin{verbatim}
try {
adq.performActions();
}
catch (Exception e) {
e.printStackTrace();
}
\end{verbatim}
\end{enumerate}
Actionable dynamic queries let you act on large numbers of entities in
smaller groups. It's an efficient and high performing way to update
entities.
\section{Related Topics}\label{related-topics-25}
\href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Creating
Local Service}
\href{/docs/7-2/appdev/-/knowledge_base/a/invoking-local-services}{Invoking
Local Services}
\chapter{REST Builder}\label{rest-builder}
\noindent\hrulefill
\textbf{Note:} This documentation is in beta. Stay tuned for more to
come!
\noindent\hrulefill
Liferay DXP's headless REST APIs follow the
\href{https://swagger.io/docs/specification/about/}{OpenAPI}
specification and let your apps consume RESTful web services. These APIs
are developed using a mixture of the Contract First and Contract Last
development approaches. This presents a best-of-both-worlds approach to
API development. For more detailed information, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/headless-rest-apis}{Headless
REST APIs}.
Here, you'll learn how to use Liferay's
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-rest-builder}{REST
Builder tool} to create headless REST APIs for your own apps. REST
Builder is an API generator that consumes OpenAPI profiles and generates
the API scaffolding: JAX-RS endpoints, parsing, XML generation, and
advanced features like filtering or multipart (binary file) support. The
developer only has to fill in the resource implementations, calling
Liferay DXP's remote services.
Read on to learn how to generate REST services with REST Builder!
\chapter{Generating APIs with REST
Builder}\label{generating-apis-with-rest-builder}
\noindent\hrulefill
\textbf{Note:} This documentation is in beta. Stay tuned for more to
come!
\noindent\hrulefill
Follow these steps to use REST Builder to create a headless REST API for
your app:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{project}.
\item
Install REST Builder. For instructions on this, see
\href{/docs/7-2/reference/-/knowledge_base/r/rest-builder-gradle-plugin}{REST
Builder Gradle Plugin}.
\item
Run \texttt{gradlew\ clean\ deploy}. Note that your Gradle wrapper may
not be in your app's project directory, so you may need to use
\texttt{..} to locate it (e.g.,
\texttt{../../../gradlew\ clean\ deploy}).
\item
Create the \texttt{*-api} and \texttt{*-impl} projects with the usual
files (\texttt{build.gradle}, \texttt{bnd.bnd}). Also create a
\texttt{rest-config.yaml} with the author, paths, and packages. For
example, here's the \texttt{rest-config.yaml} for Liferay's
\texttt{headless-delivery} API:
\begin{verbatim}
apiDir: "../headless-delivery-api/src/main/java"
apiPackagePath: "com.liferay.headless.delivery"
application:
baseURI: "/headless-delivery"
className: "HeadlessDeliveryApplication"
name: "Liferay.Headless.Delivery"
author: "Javier Gamarra"
clientDir: "../headless-delivery-client/src/main/java"
testDir: "../headless-delivery-test/src/testIntegration/java"
\end{verbatim}
\item
In your \texttt{*-impl} module's root folder, write your OpenAPI
profile in YAML. You can use the
\href{https://editor.swagger.io/}{Swagger Editor} to validate syntax
and ensure compliance with the OpenAPI specification.
\item
In your \texttt{*-impl} module folder, run \texttt{gradlew\ buildREST}
(make sure you locate your Gradle wrapper as instructed in step two
above).
\item
REST Builder generates the interfaces with the JAX-RS endpoints. It
also generates a \texttt{*ResourceImpl} class where you must implement
the business logic for each service.
\item
After implementing the business logic for each service, deploy your
modules. Your APIs are then available at this URL:
\begin{verbatim}
http://[host]:[port]/o/[APPLICATION_CLASSNAME]/[OPEN_API_VERSION]/
\end{verbatim}
You can also execute \texttt{jaxrs:check} in the OSGi console to see
all the JAX-RS endpoints.
\end{enumerate}
\section{Related Topics}\label{related-topics-26}
\href{/docs/7-2/appdev/-/knowledge_base/a/rest-builder}{REST Builder}
\href{/docs/7-2/frameworks/-/knowledge_base/f/headless-rest-apis}{Headless
REST APIs}
\href{/docs/7-2/reference/-/knowledge_base/r/rest-builder-gradle-plugin}{REST
Builder Gradle Plugin}
\chapter{Troubleshooting Application Development
Issues}\label{troubleshooting-application-development-issues}
When coding on any platform, you can sometimes run into issues that have
no clear resolution. This can be particularly frustrating. If you have
issues building, deploying, or running apps and modules, you want to
resolve them fast. These frequently asked questions and answers help you
troubleshoot and correct problems.
Here are the troubleshooting sections:
\begin{itemize}
\tightlist
\item
\hyperref[modules]{Modules}
\item
\hyperref[services-and-components]{Services and Components}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/troubleshooting-front-end-development-issues}{Front-end}
\end{itemize}
Click a question to view the answer.
\section{Modules}\label{modules}
{How can I configure dependencies on Liferay artifacts?~{}}
\begin{verbatim}
See Configuring Dependencies .
\end{verbatim}
{What are optional package imports and how can I specify them?~{}}
\begin{verbatim}
When developing modules, you can declare optional package imports. An optional package import is one your module can use if it's available, but can still function without it. Specifying optional package imports is straightforward.
\end{verbatim}
{How can I connect to a JNDI data source from my module?~{}}
\begin{verbatim}
Connecting to an application server's JNDI data sources from Liferay's OSGi environment is almost the same as connecting to them from the Java EE environment. In an OSGi environment, the only difference is that you must use Liferay DXP's class loader to load the application server's JNDI classes .
\end{verbatim}
{My module has an unresolved requirement. What can I do?~{}}
\begin{verbatim}
If one of your bundles imports a package that no other bundle in the Liferay OSGi runtime exports, Liferay DXP reports an unresolved requirement:
! could not resolve the bundles: ...
Unresolved requirement: Import-Package: ...
...
Unresolved requirement: Require-Capability ...
To satisfy the requirement, find a module that provides the capability, add it to your build file's dependencies, and deploy it .
\end{verbatim}
{An IllegalContextNameException reports that my bundle's context name
does not follow Bundle-SymbolicName syntax. How can I fix the context
name?~{}}
\begin{verbatim}
Adjust the Bundle-SymbolicName to adhere to the syntax .
\end{verbatim}
{How can I adjust my module's logging?~{}}
\begin{verbatim}
See Adjusting Module Logging .
\end{verbatim}
{How can I implement logging in my module or plugin?~{}}
\begin{verbatim}
Use Simple Logging Facade for Java (SLF4J) to log messages .
\end{verbatim}
{After creating a relational mapping between Service Builder entities,
my portlet is using too much memory. What can I do?~{}}
\begin{verbatim}
Disabling the cache related to the entity mapping lowers memory usage. .
\end{verbatim}
\section{Services and Components}\label{services-and-components}
{How can I see what's happening in the OSGi container?~{}}
\begin{verbatim}
Run a System Check. .
\end{verbatim}
{How can I detect unresolved OSGi components?~{}}
\begin{verbatim}
module components that use Service Builder use Dependency Manager (DM) and most other module components use Declarative Services (DS). Gogo shell commands and tools help you find and inspect unsatisfied component references for DM and DS components .
\end{verbatim}
{What is the safest way to call OSGi services from non-OSGi code?~{}}
\begin{verbatim}
See
\end{verbatim}
Use \texttt{category} elements to specify each class or class hierarchy
to log messages for. Set the \texttt{name} attribute to that class name
or root package. The example category sets logging for the class
hierarchy starting at package \texttt{org.foo}. Log messages at or above
the \texttt{DEBUG} log level are printed for classes in \texttt{org.foo}
and classes in packages starting with \texttt{org.foo}.
Set each category's \texttt{priority} element to the log
\href{http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html}{level}
(priority) you want.
\begin{itemize}
\tightlist
\item
ALL
\item
TRACE
\item
DEBUG
\item
INFO
\item
WARN
\item
ERROR
\item
FATAL
\item
OFF
\end{itemize}
The log messages are printed to Liferay log files in
\texttt{{[}Liferay\_Home{]}/logs}.
You can see examples of module logging in several Liferay sample
projects. For example, the
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/apps/action-command-portlet}{action-command-portlet},
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/extensions/document-action}{document-action},
and
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/apps/service-builder/jdbc}{service-builder/jdbc}
samples (among others) leverage module logging.
\noindent\hrulefill
\textbf{Note:} If the log level configuration isn't appearing (e.g., you
set the log level to \texttt{ERROR} but you're still getting
\texttt{WARN} messages), make sure the log configuration file name
prefix matches the module's symbolic name. If you have bnd installed,
output from command \texttt{bnd\ print\ {[}path-to-bundle{]}} includes
the module's symbolic name
(\href{https://github.com/bndtools/bnd/wiki/Install-bnd-on-the-command-line}{Here}
are instructions for installing bnd for the command line).
\noindent\hrulefill
That's it for module log configuration. You're all set to print the
information you want.
\section{Related Topics}\label{related-topics-27}
\href{/docs/7-2/appdev/-/knowledge_base/a/implementing-logging}{Implementing
Logging}
\chapter{Identifying Liferay Artifact Versions for
Dependencies}\label{identifying-liferay-artifact-versions-for-dependencies}
When you're developing an application using Liferay APIs or tools---for
example, you might create a Service Builder application or use Message
Bus or Asset Framework---you must determine which versions of Liferay
artifacts (modules, apps, etc.) you application's modules must specify
as dependencies. To learn how to find Liferay artifacts and configure
dependencies on them, see
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}.
\section{Related Topics}\label{related-topics-28}
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}
\chapter{Resolving Bundle-SymbolicName Syntax
Issues}\label{resolving-bundle-symbolicname-syntax-issues}
Liferay's OSGi Runtime framework sometimes throws an
\texttt{IllegalContextNameException}. Often, this is because an OSGi
bundle's \texttt{Bundle-SymbolicName} manifest header has a space in it.
The \texttt{Bundle-SymbolicName} uniquely identifies the bundle---along
with the \texttt{Bundle-Version} manifest header---and cannot contain
spaces. To follow naming best practices, use a reverse-domain name in
your \texttt{Bundle-SymbolicName}. For example, a module with the domain
\texttt{troubleshooting.liferay.com} would be reversed to
\texttt{com.liferay.troubleshooting.}.
There are three ways to specify a bundle's \texttt{Bundle-SymbolicName}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\texttt{Bundle-SymbolicName} header in a bundle's \texttt{bnd.bnd}
file.
\item
\texttt{Bundle-SymbolicName} header in a plugin WAR's
\texttt{liferay-plugin-package.properties} file.
\item
Plugin WAR file name, if the WAR's
\texttt{liferay-plugin-package.properties} has no
\texttt{Bundle-SymbolicName} header.
\end{enumerate}
For plugin WARs, specifying the \texttt{Bundle-SymbolicName} in the
\texttt{liferay-plugin-package.properties} file is preferred.
For example, if you deploy a plugin WAR that has no
\texttt{Bundle-SymbolicName} header in its
\texttt{liferay-plugin-package.properties}, the
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{WAB
Generator} uses the WAR's name as the WAB's
\texttt{Bundle-SymbolicName}. If the WAR's name has a space in it (e.g.,
\texttt{space-program-theme\ v1.war}) an
\texttt{IllegalContextNameException} occurs on deployment.
\begin{verbatim}
org.apache.catalina.core.ApplicationContext.log The context name 'space-program-theme v1' does not follow Bundle-SymbolicName syntax.
org.eclipse.equinox.http.servlet.internal.error.IllegalContextNameException: The context name 'space-program-theme v1' does not follow Bundle-SymbolicName syntax.
\end{verbatim}
However you set your a \texttt{Bundle-SymbolicName}, refrain from using
spaces.
\section{Related Topics}\label{related-topics-29}
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Using
the WAB Generator}
\chapter{Calling Non-OSGi Code that Uses OSGi
Services}\label{calling-non-osgi-code-that-uses-osgi-services}
Liferay DXP's static service utilities (e.g., \texttt{UserServiceUtil},
\texttt{CompanyServiceUtil}, \texttt{GroupServiceUtil}, etc.) are
examples of non-OSGi code that use OSGi services. Service Builder
generates them for backwards compatibility purposes only. If you're
tempted to call a \texttt{*ServiceUtil} class or your existing code
calls one, access the \texttt{*Service} directly instead using one these
alternatives:
\begin{itemize}
\item
If your class is a Declarative Services component, use an
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{\texttt{@Reference}
annotation} to access the \texttt{*Service} class.
\item
If your class isn't a Declarative Services component, use a
\href{/docs/7-2/frameworks/-/knowledge_base/f/service-trackers-for-osgi-services}{\texttt{ServiceTracker}}
to access the \texttt{*Service} class.
\end{itemize}
You can check the state of Liferay DXP's services in
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{the
Gogo shell}. The \texttt{scr:list} Gogo shell command shows all
Declarative Services components, including inactive ones from
unsatisfied dependencies. To find unsatisfied dependencies for Service
Builder services, use the Dependency Manager's
\texttt{dependencymanager:dm\ wtf} command. Note that these commands
only show components that haven't been activated because of unsatisfied
dependencies. They don't show pure service trackers that are waiting for
a service because of unsatisfied dependencies.
\section{Related Topics}\label{related-topics-30}
\href{/docs/7-2/appdev/-/knowledge_base/a/detecting-unresolved-osgi-components}{Detecting
Unresolved OSGi Components}
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix
Gogo Shell}
\chapter{Connecting to JNDI Data
Sources}\label{connecting-to-jndi-data-sources}
Connecting to an application server's JNDI data sources from Liferay
DXP's OSGi environment is almost the same as connecting to them from the
Java EE environment. In an OSGi environment, the only difference is that
you must use Liferay DXP's class loader to load the application server's
JNDI classes. The following code demonstrates this.
\begin{verbatim}
Thread thread = Thread.currentThread();
// Get the thread's class loader. You'll reinstate it after using
// the data source you look up using JNDI
ClassLoader origLoader = thread.getContextClassLoader();
// Set Liferay's class loader on the thread
thread.setContextClassLoader(PortalClassLoaderUtil.getClassLoader());
try {
// Look up the data source and connect to it
InitialContext ctx = new InitialContext();
DataSource datasource = (DataSource)
ctx.lookup("java:comp/env/jdbc/TestDB");
Connection connection = datasource.getConnection();
Statement statement = connection.createStatement();
// Execute SQL statements here ...
connection.close();
}
catch (NamingException ne) {
ne.printStackTrace();
}
catch (SQLException sqle) {
sqle.printStackTrace();
}
finally {
// Switch back to the original context class loader
thread.setContextClassLoader(origLoader);
}
\end{verbatim}
The example code sets Liferay DXP's classloader on the thread to access
the JNDI API.
\begin{verbatim}
thread.setContextClassLoader(PortalClassLoaderUtil.getClassLoader());
\end{verbatim}
It uses JNDI to look up the data source.
\begin{verbatim}
InitialContext ctx = new InitialContext();
DataSource datasource = (DataSource)
ctx.lookup("java:comp/env/jdbc/TestDB");
\end{verbatim}
After working with the data source, the code reinstates the thread's
original classloader.
\begin{verbatim}
thread.setContextClassLoader(origLoader);
\end{verbatim}
Here are the class imports for the example code:
\begin{verbatim}
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
\end{verbatim}
Your applications can use similar code to access a data source. Make
sure to substitute \texttt{jdbc/TestDB} with your data source name.
\noindent\hrulefill
\textbf{Note}: An OSGi bundle's attempt to connect to a JNDI data source
without using Liferay DXP's classloader results in a
\texttt{java.lang.ClassNotFoundException}. For example, here's an
exception from attempting to use Apache Tomcat's JNDI API without using
Liferay DXP's classloader:
\begin{verbatim}
javax.naming.NoInitialContextException: Cannot instantiate class:
org.apache.naming.java.javaURLContextFactory [Root exception is
java.lang.ClassNotFoundException:
org.apache.naming.java.javaURLContextFactory]
\end{verbatim}
\noindent\hrulefill
An easier way to work with databases is to connect to them using Service
Builder.
\chapter{Detecting Unresolved OSGi
Components}\label{detecting-unresolved-osgi-components}
Liferay DXP includes
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell} commands that come in handy when trying to diagnose a problem due
to an unresolved OSGi component. The specific tools to use depend on the
component framework of the unresolved component. Most Liferay DXP
components are developed using Declarative Services (DS), also known as
SCR (Service Component Runtime). An exception to this is Liferay DXP's
Service Builder services, which are Dependency Manager (DM) components.
Both
\href{http://felix.apache.org/documentation/subprojects/apache-felix-service-component-runtime.html}{Declarative
Services} and
\href{http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager.html}{Dependency
Manager} are Apache Felix projects.
The unresolved component troubleshooting instructions are divided into
these sections:
\begin{itemize}
\tightlist
\item
\hyperref[declarative-services-components]{Declarative Services
Components}
\begin{itemize}
\tightlist
\item
\hyperref[declarative-services-unsatisfied-component-scanner]{Declarative
Services Unsatisfied Component Scanner}
\item
\hyperref[dsunsatisfied-command]{ds:unsatisfied Command}
\end{itemize}
\item
\hyperref[service-builder-components]{Service Builder Components}
\begin{itemize}
\tightlist
\item
\hyperref[unavailable-component-scanner]{Unavailable Component
Scanner}
\item
\hyperref[dm-na-command]{dm na Command}
\item
\hyperref[serviceproxyfactory]{\texttt{ServiceProxyFactory}}
\end{itemize}
\end{itemize}
\section{Declarative Services
Components}\label{declarative-services-components}
Start with DS, since most Liferay DXP components, apart from Service
Builder components, are DS components. Suppose one of your bundle's
components has an unsatisfied service reference. How can you detect
this? Two ways:
\begin{itemize}
\item
Enable a
\hyperref[declarative-services-unsatisfied-component-scanner]{Declarative
Services Unsatisfied Component Scanner} to report unsatisfied
references automatically or
\item
Use the \hyperref[dsunsatisfied-command]{Gogo shell command
\texttt{ds:unsatisfied}} to check for them manually.
\end{itemize}
\section{Declarative Services Unsatisfied Component
Scanner}\label{declarative-services-unsatisfied-component-scanner}
Here's how to enable the unsatisfied component scanner:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a file
\texttt{com.liferay.portal.osgi.debug.declarative.service.internal.configuration.UnsatisfiedComponentScannerConfiguration.cfg}.
\item
Add the following content:
\begin{verbatim}
unsatisfiedComponentScanningInterval=5
\end{verbatim}
\item
Copy the file into \texttt{{[}LIFERAY\_HOME{]}/osgi/configs}.
\end{enumerate}
The scanner detects and logs unsatisfied service component references.
The log message describes the bundle, the referencing DS component
class, and the referenced component.
Here's an example scanner message:
\begin{verbatim}
11:18:28,881 WARN [Declarative Service Unsatisfied Component Scanner][UnsatisfiedComponentScanner:91]
Bundle {id: 631, name: com.liferay.blogs.web, version: 2.0.0}
Declarative Service {id: 3333, name: com.liferay.blogs.web.internal.portlet.action.EditEntryMVCRenderCommand, unsatisfied references:
{name: ItemSelectorHelper, target: null}
}
\end{verbatim}
The message above warns that the \texttt{com.liferay.blogs.web} bundle's
DS component
\texttt{com.liferay.blogs.web.internal.portlet.action.EditEntryMVCRenderCommand}
has an unsatisfied reference to a component of type
\texttt{ItemSelectorHelper}. The referencing component's ID (SCR ID) is
\texttt{3333} and its bundle ID is \texttt{631}.
\section{ds:unsatisfied Command}\label{dsunsatisfied-command}
Another way to detect unsatisfied component references is to invoke the
Gogo shell command \texttt{ds:unsatisfied}.
\begin{itemize}
\tightlist
\item
\texttt{ds:unsatisfied} shows all unsatisfied DS components
\item
\texttt{ds:unsatisfied\ {[}BUNDLE\_ID{]}} shows the bundle's
unsatisfied DS components
\end{itemize}
To view more detailed information about the unsatisfied DS component,
pass the component's ID to the command
\texttt{scr:info\ {[}component\ ID{]}}. For example, the following
command does this for a component with ID \texttt{1701}:
\begin{verbatim}
g! scr:info 1701
*** Bundle: org.foo.bar.command (507)
Component Description:
Name: org.foo.bar.command
Implementation Class: org.foo.bar.command.FooBarCommand
Default State: enabled
Activation: delayed
Configuration Policy: optional
Activate Method: activate
Deactivate Method: deactivate
Modified Method: -
Configuration Pid: [org.foo.bar.command]
Services:
org.foo.bar.command.DuckQuackCommand
Service Scope: singleton
Reference: Duck
Interface Name: org.foo.bar.api.Foo
Cardinality: 1..1
Policy: static
Policy option: reluctant
Reference Scope: bundle
Component Description Properties:
osgi.command.function = foo
osgi.command.scope = bar
Component Configuration:
ComponentId: 1701
State: unsatisfied reference
UnsatisfiedReference: Foo
Target: null
(no target services)
Component Configuration Properties:
component.id = 1701
component.name = org.foo.bar.command
osgi.command.function = foo
osgi.command.scope = bar
\end{verbatim}
In the \texttt{Component\ Configuration} section,
\texttt{UnsatisfiedReference} lists the unsatisfied reference's type.
This bundle's component isn't working because it's missing a
\texttt{Foo} service. Now you can focus on why \texttt{Foo} is
unavailable. The solution may be as simple as starting or deploying a
bundle that provides the \texttt{Foo} service.
\section{Service Builder Components}\label{service-builder-components}
Service Builder modules are implemented using Spring. Liferay DXP uses
\href{http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager.html}{the
Apache Felix Dependency Manager} to manage Service Builder module OSGi
components via the
\href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.portal.spring.extender/}{Portal
Spring Extender} module.
When developing a Liferay Service Builder application, you might
sometimes have an unresolved Spring-related OSGi component. This can
occur if you update your application's database schema but forget to
trigger an upgrade.
These features detect unresolved Service Builder related components.
\begin{itemize}
\tightlist
\item
\hyperref[unavailable-component-scanner]{Unavailable Component
Scanner}
\item
\hyperref[dm-na-command]{dm na Command}
\item
\hyperref[serviceproxyfactory]{ServiceProxyFactory}
\end{itemize}
\section{Unavailable Component
Scanner}\label{unavailable-component-scanner}
The
\href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.portal.osgi.debug.spring.extender/}{OSGi
Debug Spring Extender} module's Unavailable Component Scanner reports
missing components in modules that use Service Builder. Here's how to
enable the scanner:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create the configuration file
\texttt{com.liferay.portal.osgi.debug.spring.extender.internal.configuration.UnavailableComponentScannerConfiguration.cfg}.
\item
In the configuration file, set the time interval (in seconds) between
scans:
\texttt{unavailableComponentScanningInterval=5}
\item
Copy the file into \texttt{{[}LIFERAY\_HOME{]}/osgi/configs}.
\end{enumerate}
The scanner reports Spring extender dependency manager component status
on the set interval. If all components are registered, the scanner sends
a confirmation message.
\begin{verbatim}
11:10:53,817 INFO [Spring Extender Unavailable Component Scanner][UnavailableComponentScanner:166] All Spring extender dependency manager components are registered
\end{verbatim}
If a component is unavailable, it warns you:
\begin{verbatim}
11:13:08,851 WARN [Spring Extender Unavailable Component Scanner][UnavailableComponentScanner:173] Found unavailable component in bundle com.liferay.screens.service_1.0.28 [516].
Component ComponentImpl[null com.liferay.portal.spring.extender.internal.context.ModuleApplicationContextRegistrator@1541eee] is unavailable due to missing required dependencies: ServiceDependency[interface com.liferay.blogs.service.BlogsEntryService null].
\end{verbatim}
Component unavailability, such as what's reported above, can occur when
DS components and Service Builder components are published and used in
the same module. Use separate modules to publish DS components and
Service Builder components.
\section{dm na Command}\label{dm-na-command}
Dependency Manager's
\href{http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager/tutorials/leveraging-the-shell.html}{Gogo
shell command \texttt{dm}} lists all Service Builder components, their
required services, and whether each required service is available.
To list unresolved components only execute this Gogo shell command:
\begin{verbatim}
dm na
\end{verbatim}
The \texttt{na} option stands for ``not available.''
\section{ServiceProxyFactory}\label{serviceproxyfactory}
Liferay DXP's logs report unresolved Service Builder components too. For
example, Liferay DXP logs an error when a Service Proxy Factory can't
create a new instance of a Service Builder based entity because a
service component is unresolved.
The following code demonstrates using a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ServiceProxyFactory.html}{\texttt{ServiceProxyFactory}
class} to create a new entity instance:
\begin{verbatim}
private static volatile MessageBus _messageBus =
ServiceProxyFactory.newServiceTrackedInstance(
MessageBus.class, MessageBusUtil.class, "_messageBus", true);
\end{verbatim}
This message alerts you to the unavailable service:
\begin{verbatim}
11:07:35,139 ERROR [localhost-startStop-1][ServiceProxyFactory:265] Service "com.liferay.portal.kernel.messaging.sender.SingleDestinationMessageSenderFactory" is unavailable in 60000 milliseconds while setting field "_singleDestinationMessageSenderFactory" for class "com.liferay.portal.kernel.messaging.sender.SingleDestinationMessageSenderFactoryUtil", will retry...
\end{verbatim}
Based on the message above, there's no bundle providing the service
\texttt{com.liferay.portal.kernel.messaging.sender.SingleDestinationMessageSenderFactory}.
Now you can detect unresolved components, DS and DM components,
automatically using scanners, manually using Gogo shell commands, and
programmatically using a \texttt{ServiceProxyFactory}.
\section{Related Topics}\label{related-topics-31}
\href{/docs/7-2/appdev/-/knowledge_base/a/system-check}{System Check}
\chapter{Disabling Cache for Table Mapper
Tables}\label{disabling-cache-for-table-mapper-tables}
Service Builder creates relational mappings between entities. It uses
mapping tables to associate the entities. In your \texttt{service.xml}
file, both entities have a \texttt{mapping-table} column attribute of
the format \texttt{mapping-table="table1\_table2"}. For example, a
\texttt{service.xml} that maps \texttt{AssetEntry}s to
\texttt{AssetCategory}s has an \texttt{AssetCategory} entity with this
column:
\begin{verbatim}
\end{verbatim}
and an \texttt{AssetEntry} entity element with this column:
\begin{verbatim}
\end{verbatim}
By default, a table mapper cache is associated with each mapping table.
The cache optimizes object retrieval. In some cases, however, it's best
to disable a table mapper cache.
\section{Why would I want to disable cache on a table
mapper?}\label{why-would-i-want-to-disable-cache-on-a-table-mapper}
Super-large entity tables can result in a memory-hogging table mapper
cache. For this reason, consider disabling cache on a table mapper.
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Table\%20Mapper}{\texttt{table.mapper.cacheless.mapping.table.names}
Portal property} disables cache for table mappers associated with the
specified mapping tables. Here's the default property setting:
\begin{verbatim}
##
## Table Mapper
##
#
# Set a list of comma delimited mapping table names that will not be using
# cache in their table mappers.
#
table.mapper.cacheless.mapping.table.names=\
Users_Groups,\
Users_Orgs,\
Users_Roles,\
Users_Teams,\
Users_UserGroups
\end{verbatim}
All of the disabled caches above pertain to the \texttt{User} object
because the table mappers tend to be much too large to have a useful
cache---each \texttt{User} can have several entries in each related
table.
Potential race conditions retrieving objects from the cache is another
reason to disable a table mapper.
For example,
\href{https://issues.liferay.com/browse/LPS-84374}{LPS-84374} describes
a race condition in which a custom entity's table mapper cache can be
cleared while in use, causing transactional rollbacks. Publishing
\texttt{AssetEntry}s clears all associated table mapper caches. If
they're published at the same time getter methods are retrieving objects
from the \texttt{AssetEntries\_AssetCategories} mapping table,
transaction rollbacks occur.
\section{Disabling a Table Mapper
Cache}\label{disabling-a-table-mapper-cache}
Adding a mapping table name to the
\texttt{table.mapper.cacheless.mapping.table.names} Portal property
disables the associated table mapper cache.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your
\href{/docs/7-1/deploy/-/knowledge_base/d/liferay-home}{\texttt{{[}Liferay\_Home{]}/portal-ext.properties}
file}, add the current
\texttt{table.mapper.cacheless.mapping.table.names} property setting.
The setting is in your Liferay DXP installation's
\texttt{portal-impl.jar/portal.properties} file.
\item
Append your mapping table name to the list. For example, to disable
the cache associated with a mapping table named
\texttt{AssetEntries\_AssetCategories}, add that name to the list.
\begin{verbatim}
table.mapper.cacheless.mapping.table.names=\
Users_Groups,\
Users_Orgs,\
Users_Roles,\
Users_Teams,\
Users_UserGroups,\
AssetEntries_AssetCategories
\end{verbatim}
\item
Restart the Liferay DXP instance to delete the table mapper cache.
\end{enumerate}
You've disabled an unwanted table mapper cache.
\chapter{Implementing Logging}\label{implementing-logging}
7.0 uses the Log4j logging framework, but it may be replaced in the
future. It's a best practice to use \href{https://www.slf4j.org/}{Simple
Logging Facade for Java (SLF4J)} to log messages in your modules and
traditional plugins. SLF4J is already integrated into Liferay DXP, so
you can focus on logging messages.
Here's how to use SLF4J to log messages in a class:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add a private static SLF4J
\href{https://www.slf4j.org/apidocs/org/slf4j/Logger.html}{\texttt{Logger}
field}.
\begin{verbatim}
private static Logger _logger;
\end{verbatim}
\item
Instantiate the logger.
\begin{verbatim}
_logger = LoggerFactory.getLogger(this.getClass().getName());
\end{verbatim}
\item
Throughout your class, log messages where noteworthy things happen.
For example,
\begin{verbatim}
_logger.debug("...");
_logger.warn("...");
_logger.error("...");
...
\end{verbatim}
Use \texttt{Logger} methods appropriate for each message:
\begin{itemize}
\tightlist
\item
\texttt{trace}: Provides more information than debug. This is the
most verbose message level.
\item
\texttt{debug}: Event and application information helpful for
debugging.
\item
\texttt{info}: High level events.
\item
\texttt{warn}: Information that might, but does not necessarily,
indicate a problem.
\item
\texttt{error}: Normal errors. This is the least verbose message
level.
\end{itemize}
\end{enumerate}
Log verbosity should correlate with the log level set for the class or
package. Make sure you provide additional information at log levels
expected to be more verbose, such as \texttt{info} and \texttt{debug}.
You're all set to add logging to your modules and traditional plugins.
\section{Related Topics}\label{related-topics-32}
\href{/docs/7-2/appdev/-/knowledge_base/a/adjusting-module-logging}{Adjusting
Module Logging}
\chapter{Declaring Optional Import Package
Requirements}\label{declaring-optional-import-package-requirements}
When developing modules, you can declare \emph{optional} dependencies.
An optional dependency is one your module can use if available, but can
still function without it.
\noindent\hrulefill
\textbf{Important:} Try to avoid optional dependencies. The best module
designs rely on normal dependencies. If an optional dependency seems
desirable, your module may be trying to provide more than one distinct
type of functionality. In such a situation, it's best to split it into
multiple modules that provide smaller, more focused functionality.
\noindent\hrulefill
If you decide that your module requires an optional dependency, follow
these steps to add it:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your module's \texttt{bnd.bnd} file, declare the package your
module optionally depends on:
\begin{verbatim}
Import-Package: com.liferay.demo.foo;resolution:="optional"
\end{verbatim}
Note that you can use either an optional or dynamic import. The
differences are explained
\href{https://osgi.org/specification/osgi.core/7.0.0/framework.module.html\#i2548181}{here}.
\item
Create a component to use the optional package:
\begin{verbatim}
import com.liferay.demo.foo.Foo; // A class from the optional package
@Component(
enabled = false // instruct declarative services to ignore this component by default
)
public class OptionalPackageConsumer implements Foo {...}
\end{verbatim}
\item
Create a second component to be a controller for the first. The second
component checks the class loader for the optional class on the
classpath. If it's not there, this means you must catch any
\texttt{ClassNotFoundException}. For example:
\begin{verbatim}
@Component
public class OptionalPackageConsumerStarter {
@Activate
void activate(ComponentContext componentContext) {
try {
Class.forName(com.liferay.demo.foo.Foo.class.getName());
componentContext.enableComponent(OptionalPackageConsumer.class.getName());
}
catch (Throwable t) {
_log.warn("Could not find {}", t.getMessage()); // Could use _log.info instead
}
}
}
\end{verbatim}
\end{enumerate}
If the class loader check in the controller component is successful, the
client component is enabled. This check is automatically performed
whenever there are any wiring changes to the module containing these
components (Declarative Services components are always restarted when
there are wiring changes).
If you install the module when the optional dependency is missing from
Liferay DXP's OSGi runtime, your controller component catches a
\texttt{ClassNotFoundException} and logs a warning or info message (or
takes whatever other action you implement to handle this case). If you
install the optional dependency, refreshing your module triggers the
OSGi bundle lifecycle events that trigger your controller's
\texttt{activate} method and the check for the optional dependency.
Since the dependency exists, your client component uses it.
Note that you can refresh a bundle from
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell} with this command:
\begin{verbatim}
equinox:refresh [bundle ID]
\end{verbatim}
\section{Related Topics}\label{related-topics-33}
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}
\chapter{Resolving Bundle
Requirements}\label{resolving-bundle-requirements}
If one of your bundles needs a package that is not exported by any other
bundle in the Liferay OSGi runtime, you get a bundle exception. Here's
an example exception:
\begin{verbatim}
! could not resolve the bundles: [com.liferay.messaging.client.command-1.0.0.201707261701 org.osgi.framework.BundleException: Could not resolve module: com.liferay.messaging.client.command [1]
Unresolved requirement: Import-Package: com.liferay.messaging.client.api; version="[1.0.0,2.0.0)"
-> Export-Package: com.liferay.messaging.client.api; bundle-symbolic-name="com.liferay.messaging.client.provider"; bundle-version="1.0.0.201707261701"; version="1.0.0"; uses:="org.osgi.framework"
com.liferay.messaging.client.provider [2]
Unresolved requirement: Import-Package: com.liferay.messaging; version="[1.0.0,2.0.0)"
-> Export-Package: com.liferay.messaging; bundle-symbolic-name="com.liferay.messaging.api"; bundle-version="1.0.0"; version="1.0.0"; uses:="com.liferay.petra.concurrent"
com.liferay.messaging.api [12]
Unresolved requirement: Import-Package: com.liferay.petra.io; version="[1.0.0,2.0.0)"
-> Export-Package: com.liferay.petra.io; bundle-symbolic-name="com.liferay.petra.io"; bundle-version="1.0.0"; version="1.0.0"
com.liferay.petra.io [16]
Unresolved requirement: Require-Capability osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"
\end{verbatim}
The first line states \emph{could not resolve the bundles}. What follows
is a string of requirements that Liferay's OSGi Runtime could not
resolve.
The bundle exception message follows this general pattern:
\begin{itemize}
\tightlist
\item
Module A has an unresolved requirement (package or capability)
\texttt{aaa.bbb}.
\item
Module B provides \texttt{aaa.bbb} but has an unresolved requirement
\texttt{ccc.ddd}.
\item
Module C provides \texttt{ccc.ddd} but has an unresolved requirement
\texttt{eee.fff}.
\item
etc.
\item
Module Z provides \texttt{www.xxx} but has an unresolved requirement
\texttt{yyy.zzz}.
\end{itemize}
The pattern stops at the final unsatisfied requirement. The last
module's dependencies are key to resolving the bundle exception. There
are two possible causes:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
A dependency that satisfies the final requirement might be missing
from the build file.
\item
A dependency that satisfies the final requirement might not be
deployed.
\end{enumerate}
Both cases require deploying a bundle that provides the missing
requirement.
The example bundle exception concludes that module
\texttt{com.liferay.petra.io} requires capability
\texttt{osgi.extender;\ filter:="(osgi.extender=osgi.serviceloader.processor)"}.
To resolve the requirement, make sure all of
\texttt{com.liferay.petra.io}'s dependencies are deployed.
The \texttt{com.liferay.petra.io} module's \texttt{build.gradle} file
lists its dependencies:
\begin{verbatim}
dependencies {
provided group: "com.liferay", name: "com.liferay.petra.concurrent", version: "1.0.0"
provided group: "com.liferay", name: "com.liferay.petra.memory", version: "1.0.0"
provided group: "org.apache.aries.spifly", name: "org.apache.aries.spifly.dynamic.bundle", version: "1.0.8"
provided group: "org.slf4j", name: "slf4j-api", version: "1.7.2"
testCompile group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "default"
}
\end{verbatim}
Then use
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix
Gogo Shell's \texttt{lb\ command}} to verify the dependencies are in
Liferay's OSGi Runtime:
\begin{verbatim}
lb
START LEVEL 1
ID|State |Level|Name
0|Active | 0|OSGi System Bundle (3.10.100.v20150529-1857)|3.10.100.v20150529-1857
1|Active | 1|com.liferay.messaging.client.command (1.0.0.201707261923)|1.0.0.201707261923
2|Active | 1|com.liferay.messaging.client.provider (1.0.0.201707261927)|1.0.0.201707261927
3|Active | 1|Apache Felix Configuration Admin Service (1.8.8)|1.8.8
4|Active | 1|Apache Felix Log Service (1.0.1)|1.0.1
5|Active | 1|Apache Felix Declarative Services (2.0.2)|2.0.2
6|Active | 1|Meta Type (1.4.100.v20150408-1437)|1.4.100.v20150408-1437
7|Active | 1|org.osgi:org.osgi.service.metatype (1.3.0.201505202024)|1.3.0.201505202024
8|Active | 1|Apache Felix Gogo Command (0.16.0)|0.16.0
9|Active | 1|Apache Felix Gogo Runtime (0.16.2)|0.16.2
10|Active | 1|Apache Felix Gogo Runtime (1.0.0)|1.0.0
...
\end{verbatim}
The dependency module \texttt{org.apache.aries.spifly.dynamic.bundle} is
missing from the runtime bundle list. The
\texttt{org.apache.aries.spifly.dynamic.bundle} module's
\texttt{MANIFEST.MF} file shows it provides the requirement capability
\texttt{osgi.extender;\ filter:="(osgi.extender=osgi.serviceloader.processor)"}:
\begin{verbatim}
Provide-Capability: osgi.extender;osgi.extender="osgi.serviceloader.regi
strar";version:Version="1.0",osgi.extender;osgi.extender="osgi.servicel
oader.processor";version:Version="1.0"
\end{verbatim}
This capability
\texttt{osgi.extender;\ filter:="(osgi.extender=osgi.serviceloader.processor)"}
is the unresolved requirement we identified earlier. Deploying this
missing bundle \texttt{org.apache.aries.spifly.dynamic.bundle} satisfies
the example module's requirement and allows the module to resolve and
install.
You can resolve your bundle exceptions by following steps similar to
these.
\noindent\hrulefill
Note: Bndtools's \emph{Resolve} button can resolve bundle dependencies
automatically. You specify the bundles your application requires and
Bndtools adds transitive dependencies from your configured artifact
repository.
\noindent\hrulefill
\section{Related Topics}\label{related-topics-34}
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Adding
Third Party Libraries to a Module}
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix
Gogo Shell}
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts}{Finding
Artifacts}
\chapter{Resolving ClassNotFoundException and NoClassDefFoundError in
OSGi
Bundles}\label{resolving-classnotfoundexception-and-noclassdeffounderror-in-osgi-bundles}
\texttt{ClassNotFoundException} and \texttt{NoClassDefFoundError} are
common, well known exceptions:
\begin{itemize}
\tightlist
\item
\texttt{ClassNotFoundException} is thrown when looking up a class that
isn't on the classpath or using an invalid name to look up a class
that isn't on the runtime classpath.
\item
\texttt{NoClassDefFoundError} occurs when a compiled class references
another class that isn't on the runtime classpath.
\end{itemize}
In OSGi environments, however, there are additional cases where a
\texttt{ClassNotFoundException} or \texttt{NoClassDefFoundError} can
occur:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
The missing class belongs to a module dependency that's an OSGi
module.
\item
The missing class belongs to a module dependency that's \emph{not} an
OSGi module.
\item
The missing class belongs to a global library, either at the Liferay
DXP web app scope or the application server scope.
\item
The missing class belongs to a Java runtime package.
\end{enumerate}
This tutorial explains how to handle each case.
\section{Case 1: The Missing Class Belongs to an OSGi
Module}\label{case-1-the-missing-class-belongs-to-an-osgi-module}
In this case, there are two possible causes:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\textbf{The module doesn't import the class's package:} For a module
(or WAB) to consume another module's exported class, the consuming
module must import the exported package that contains the class. To do
this, you add an \texttt{Import-Package} header in the consuming
module's \texttt{bnd.bnd} file. If the consuming module tries to
access the class without importing the package, a
\texttt{ClassNotFoundException} or \texttt{NoClassDefFoundError}
occurs.
Check the package name and make sure the consuming module imports the
right package. If the import is correct but you still get the
exception or error, the class might no longer exist in the package.
\item
\textbf{The class no longer exists in the imported package:} Modules
are changed frequently in OSGi runtime environments. If you reference
another module's class that its developer removed, a
\texttt{NoClassDefFoundError} or \texttt{ClassNotFoundException}
occurs. \href{http://semver.org}{Semantic Versioning} guards against
this scenario: removing a class from an exported package constitutes a
new major version for that package. Neglecting to increment the
package's major version breaks dependent modules.
For example, say a module that consumes the class \texttt{com.foo.Bar}
specifies the package import
\texttt{com.foo;version={[}1.0.0,\ 2.0.0)}. The module uses
\texttt{com.foo} versions from \texttt{1.0.0} up to (but not
including) \texttt{2.0.0}. The first part of the version number (the
\texttt{1} in \texttt{1.0.0}) represents the \emph{major} version. The
consuming module doesn't expect any \emph{major} breaking changes,
like a class removal. Removing \texttt{com.foo.Bar} from
\texttt{com.foo} without incrementing the package to a new major
version (e.g., \texttt{2.0.0}) causes a
\texttt{ClassNotFoundException} or \texttt{NoClassDefFoundError} when
other modules look up or reference that class.
You have limited options when the class no longer exists in the
package:
\begin{itemize}
\item
Adapt to the new API. To learn how to do this, read the
package's/module's Javadoc, release notes, and/or formal
documentation. You can also ask the author or search forums.
\item
Revert to the module version you used previously. Deployed module
versions reside in \texttt{{[}Liferay\_Home{]}/osgi/}. For details,
see
\href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{Backing
up Liferay Installations}.
\end{itemize}
Do what you think is best to get your module working properly.
\end{enumerate}
Now you know how to resolve common situations involving
\texttt{ClassNotFoundException} or \texttt{NoClassDefFoundError}. For
additional information on \texttt{NoClassDefFoundError}, see OSGi
Enroute's article
\href{http://enroute.osgi.org/faq/class-not-found-exception.html}{What
is NoClassDefFoundError?}.
\section{Case 2: The Missing Class Doesn't Belong to an OSGi
Module}\label{case-2-the-missing-class-doesnt-belong-to-an-osgi-module}
In this case, you have two options:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Convert the dependency into an OSGi module so it can export the
missing class. Converting a non-OSGi \texttt{JAR} file dependency into
an OSGi module that you can deploy alongside your application is the
ideal solution, so it should be your first choice.
\item
Embed the dependency in your module by embedding the dependency
\texttt{JAR} file's packages as private packages in your module. If
you want to embed a non-OSGi \texttt{JAR} file in your application,
see
\href{https://portal.liferay.dev/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Resolving
Third Party Library Package Dependencies}.
\end{enumerate}
\section{Case 3: The Missing Class Belongs to a Global
Library}\label{case-3-the-missing-class-belongs-to-a-global-library}
In this case, you can configure Liferay DXP so the OSGi system module
exports the missing class's package. Then your module can import it. You
should \textbf{NOT}, however, undertake this lightly. If Liferay
intended to make a global library available for use by developers, the
system module would already export this library! Proceed only if you
have no other solution, and watch out for unintended consequences. There
are two ways to export the package:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your \texttt{portal-ext.properties} file, use the property
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Module\%20Framework}{\texttt{module.framework.system.packages.extra}}
to specify the packages to export. Preserve the property's current
list.
\item
If the package you need is from a Liferay DXP JAR, you might be able
to add the module to the list of exported packages in
\texttt{{[}LIFERAY\_HOME{]}/osgi/core/com.liferay.portal.bootstrap.jar}'s
\texttt{META-INF/system.packages.extra.bnd} file. Try this option only
if the first option doesn't work.
\end{enumerate}
If the package you need is from a Liferay DXP module, (i.e., it's
\textbf{NOT} from a global library), you can add the package to that
module's \texttt{bnd.bnd} exports. You should \textbf{NOT}, however,
undertake this lightly. The package would already be exported if Liferay
intended for it to be available.
\section{Case 4: The Missing Class Belongs to a Java Runtime
Package}\label{case-4-the-missing-class-belongs-to-a-java-runtime-package}
\texttt{rt.jar} (the JRE library) has non-public packages. If your
module imports one of them, configure Liferay DXP's system bundle to
export the package to the module framework.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the current
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Module\%20Framework}{\texttt{module.framework.system.packages.extra}
property setting} to a
\href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}
file}. Your server's current setting is in the Liferay DXP web
application's \texttt{/WEB-INF/lib/portal-impl.jar/portal.properties}
file.
\item
In your \texttt{portal-ext.properties} file, append the required Java
runtime package to the end of the
\texttt{module.framework.system.packages.extra} property's package
list.
\item
Restart your server.
\end{enumerate}
Your module should resolve and install.
\section{Related Topics}\label{related-topics-35}
\href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{Backing
up Liferay Installations}
\href{https://portal.liferay.dev/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Resolving
Third Party Library Package Dependencies}
\chapter{System Check}\label{system-check}
During development, all kinds of strange things can happen in the OSGi
container. Liferay's \texttt{system:check}
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell} command can help you see what's happening. You can enable it to
run as the last Portal startup step and you can execute it any time in
Gogo shell.
\texttt{system:check} aggregates these commands:
\begin{itemize}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/detecting-unresolved-osgi-components\#dsunsatisfied-command}{\texttt{ds:unsatisfied}}:
Reports unsatisfied Declarative Service components.
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/detecting-unresolved-osgi-components\#dm-na-command}{\texttt{dm\ na}}:
Reports unsatisfied Dependency Manager service components, including
Service Builder services.
\end{itemize}
System checking functionality from future Liferay tools will be added to
\texttt{system:check}.
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Developer
mode} runs \texttt{system:check} automatically on every startup.
You can enable \texttt{system:check} to run on startup outside of
developer mode by setting this property in your
\texttt{portal-ext.properties} file:
\begin{verbatim}
module.framework.properties.initial.system.check.enabled=true
\end{verbatim}
As stated previously, you can run the \texttt{system:check} command any
time in
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell}. Enjoy detecting unresolved components and other issues fast
using \texttt{system:check}.
\section{Related Topics}\label{related-topics-36}
\href{/docs/7-2/appdev/-/knowledge_base/a/detecting-unresolved-osgi-components}{Detecting
Unresolved OSGi Components}
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell}
\chapter{Troubleshooting Front-End Development
Issues}\label{troubleshooting-front-end-development-issues}
Front-end development involves many moving parts. Sometimes it's hard to
tell what may be causing the issues you run into along the way. This can
be particularly frustrating. These frequently asked questions and
answers help you troubleshoot and correct problems arising during
front-end development.
Here are the troubleshooting sections:
\begin{itemize}
\tightlist
\item
\hyperref[css]{CSS}
\item
\hyperref[modules]{Modules}
\item
\hyperref[portlets]{Portlets}
\end{itemize}
Click a question to view the answer.
\section{CSS}\label{css}
\phantomsection\label{broken-css-angular-app}
{Why are my CSS templates not applied in my Angular app?~{}}
\begin{verbatim}
A known bug with Angular causes absolute URLs for CSS files not to be recognized.
Due to the nature of portals, a relative URL is not an option either because the app can be placed on any page.
To fix this, you can either provide the CSS with a theme or themelet, or you can specify the path to the CSS file with the com.liferay.portlet.header-portlet-css property in the portlet containing your Angular code.
\end{verbatim}
\phantomsection\label{portal-css-broken-ie}
{Why is Liferay Portal's CSS broken in Internet Explorer?~{}}
\begin{verbatim}
By default CSS files are minified in the browser. This can cause issues in Internet Explorer. You can disable this behavior by including theme.css.fast.load=false and minifier.enabled=false in your portal-ext.properties file.
\end{verbatim}
\section{Modules}\label{modules-1}
\phantomsection\label{jquery-anonymous-module-error}
{Why does my JQuery module throw an anonymous module error when I try to
load it?~{}}
\begin{verbatim}
If you're using an external library that you host, you must disable the Expose Global option as described in the Using External JavaScript Libraries tutorial.
\end{verbatim}
\phantomsection\label{source-maps-not-showing}
{Why are my source maps not showing for my Angular or Typescript
module?~{}}
\begin{verbatim}
This is due to LPS-83052 .
To solve this, activate the inlineSources compiler option via argument or your tsconfig.json file.
\end{verbatim}
\phantomsection\label{disable-bundler-analytics}
{I'm using the liferay-npm-bundler for multiple projects. How can I
disable analytics tracking for the liferay-npm-bundler in my
projects?~{}}
\begin{verbatim}
There are a couple options you can use to disable reporting:
Create a .liferay-npm-bundler-no-tracking file in your project's root folder, or any of its ancestors, to disable reporting.
This equates to answering No to the May liferay-npm-bundler anonymously report usage statistics to improve the tool over time? question.
\end{verbatim}
\section{Portlets}\label{portlets}
\phantomsection\label{angular-react-vue-portlet-disable-spa}
{I want to use a custom router in my Angular/React/Vue portlet. How can
I disable the default Senna JS SPA engine in my portlet?~{}}
\begin{verbatim}
By default, the Senna JS SPA engine is enabled in your portlets and sites. This disables full page reloads during portlet navigation.
If you want to use a custom router in your portlet instead, follow the instructions in the SPA documentation to blacklist your portlet from SPA.
\end{verbatim}
================================================
FILE: book/developer/customization.aux
================================================
\relax
\providecommand{\transparent@use}[1]{}
\providecommand\hyper@newdestlabel[2]{}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {150}Liferay Customization}{399}{chapter.150}\protected@file@percent }
\newlabel{liferay-customization}{{150}{399}{Liferay Customization}{chapter.150}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {151}Fundamentals}{401}{chapter.151}\protected@file@percent }
\newlabel{fundamentals}{{151}{401}{Fundamentals}{chapter.151}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {152}Configuring Dependencies}{403}{chapter.152}\protected@file@percent }
\newlabel{configuring-dependencies}{{152}{403}{Configuring Dependencies}{chapter.152}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {153}Finding Artifacts}{405}{chapter.153}\protected@file@percent }
\newlabel{finding-artifacts}{{153}{405}{Finding Artifacts}{chapter.153}{}}
\@writefile{toc}{\contentsline {section}{\numberline {153.1}Finding Core Artifact Attributes}{405}{section.153.1}\protected@file@percent }
\newlabel{finding-core-artifact-attributes}{{153.1}{405}{Finding Core Artifact Attributes}{section.153.1}{}}
\gdef \LT@ii {\LT@entry
{1}{105.70474pt}\LT@entry
{1}{134.6858pt}\LT@entry
{1}{81.00111pt}\LT@entry
{1}{88.6722pt}\LT@entry
{1}{59.69113pt}}
\gdef \LT@iii {\LT@entry
{1}{177.65813pt}\LT@entry
{1}{292.09688pt}}
\@writefile{toc}{\contentsline {section}{\numberline {153.2}Finding Liferay App and Independent Artifacts}{407}{section.153.2}\protected@file@percent }
\newlabel{finding-liferay-app-and-independent-artifacts}{{153.2}{407}{Finding Liferay App and Independent Artifacts}{section.153.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {153.3}App Manager}{407}{section.153.3}\protected@file@percent }
\newlabel{app-manager}{{153.3}{407}{App Manager}{section.153.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {153.1}{\ignorespaces You can inspect deployed module artifact IDs and version numbers.}}{408}{figure.153.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {153.2}{\ignorespaces The App Manager aggregates Liferay and independent modules.}}{408}{figure.153.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {153.4}Reference Docs}{408}{section.153.4}\protected@file@percent }
\newlabel{reference-docs}{{153.4}{408}{Reference Docs}{section.153.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {153.3}{\ignorespaces Results from this Gogo command show that the module's number is \texttt {1173}.}}{409}{figure.153.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {153.4}{\ignorespaces Results from running the \texttt {headers} command show the module's bundle vendor and bundle version.}}{409}{figure.153.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {153.5}{\ignorespaces Liferay DXP app Javadoc overviews list each app module's display name, followed by its group ID, artifact ID, and version number in a colon-separated string. It's a Gradle artifact syntax.}}{410}{figure.153.5}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {153.5}Maven Central}{410}{section.153.5}\protected@file@percent }
\newlabel{maven-central}{{153.5}{410}{Maven Central}{section.153.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {153.6}Related Topics}{411}{section.153.6}\protected@file@percent }
\newlabel{related-topics}{{153.6}{411}{Related Topics}{section.153.6}{}}
\gdef \LT@iv {\LT@entry
{1}{62.14067pt}\LT@entry
{1}{56.93881pt}\LT@entry
{1}{63.67303pt}\LT@entry
{1}{43.04385pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {154}Specifying Dependencies}{413}{chapter.154}\protected@file@percent }
\newlabel{specifying-dependencies}{{154}{413}{Specifying Dependencies}{chapter.154}{}}
\@writefile{toc}{\contentsline {section}{\numberline {154.1}Related Topics}{414}{section.154.1}\protected@file@percent }
\newlabel{related-topics-1}{{154.1}{414}{Related Topics}{section.154.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {155}Resolving Third Party Library Package Dependencies}{415}{chapter.155}\protected@file@percent }
\newlabel{resolving-third-party-library-package-dependencies}{{155}{415}{Resolving Third Party Library Package Dependencies}{chapter.155}{}}
\@writefile{toc}{\contentsline {section}{\numberline {155.1}Library Package Resolution Workflow}{416}{section.155.1}\protected@file@percent }
\newlabel{library-package-resolution-workflow}{{155.1}{416}{Library Package Resolution Workflow}{section.155.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {155.2}Embedding Libraries in a Project}{417}{section.155.2}\protected@file@percent }
\newlabel{embedding-libraries-in-a-project}{{155.2}{417}{Embedding Libraries in a Project}{section.155.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {155.3}Embedding Libraries Using Gradle}{417}{section.155.3}\protected@file@percent }
\newlabel{embedding-libraries-using-gradle}{{155.3}{417}{Embedding Libraries Using Gradle}{section.155.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {155.4}Embedding a Library Using Maven}{417}{section.155.4}\protected@file@percent }
\newlabel{embedding-a-library-using-maven}{{155.4}{417}{Embedding a Library Using Maven}{section.155.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {155.5}Related Topics}{418}{section.155.5}\protected@file@percent }
\newlabel{related-topics-2}{{155.5}{418}{Related Topics}{section.155.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {156}Understanding Excluded JARs}{419}{chapter.156}\protected@file@percent }
\newlabel{understanding-excluded-jars}{{156}{419}{Understanding Excluded JARs}{chapter.156}{}}
\@writefile{toc}{\contentsline {section}{\numberline {156.1}Related Topics}{420}{section.156.1}\protected@file@percent }
\newlabel{related-topics-3}{{156.1}{420}{Related Topics}{section.156.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {157}Using the Felix Gogo Shell}{421}{chapter.157}\protected@file@percent }
\newlabel{using-the-felix-gogo-shell}{{157}{421}{Using the Felix Gogo Shell}{chapter.157}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {158}Importing Packages}{423}{chapter.158}\protected@file@percent }
\newlabel{importing-packages}{{158}{423}{Importing Packages}{chapter.158}{}}
\@writefile{toc}{\contentsline {section}{\numberline {158.1}Automatic Package Import Generation}{423}{section.158.1}\protected@file@percent }
\newlabel{automatic-package-import-generation}{{158.1}{423}{Automatic Package Import Generation}{section.158.1}{}}
\gdef \LT@v {\LT@entry
{1}{130.8321pt}\LT@entry
{1}{338.9229pt}}
\@writefile{toc}{\contentsline {section}{\numberline {158.2}Manually Adding Package Imports}{425}{section.158.2}\protected@file@percent }
\newlabel{manually-adding-package-imports}{{158.2}{425}{Manually Adding Package Imports}{section.158.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {158.3}Related Topics}{426}{section.158.3}\protected@file@percent }
\newlabel{related-topics-4}{{158.3}{426}{Related Topics}{section.158.3}{}}
\gdef \LT@vi {\LT@entry
{3}{149.19315pt}\LT@entry
{3}{211.04694pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {159}Exporting Packages}{427}{chapter.159}\protected@file@percent }
\newlabel{exporting-packages}{{159}{427}{Exporting Packages}{chapter.159}{}}
\@writefile{toc}{\contentsline {section}{\numberline {159.1}Related Topics}{428}{section.159.1}\protected@file@percent }
\newlabel{related-topics-5}{{159.1}{428}{Related Topics}{section.159.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {160}Semantic Versioning}{429}{chapter.160}\protected@file@percent }
\newlabel{semantic-versioning}{{160}{429}{Semantic Versioning}{chapter.160}{}}
\@writefile{toc}{\contentsline {section}{\numberline {160.1}Baselining Your Project}{429}{section.160.1}\protected@file@percent }
\newlabel{baselining-your-project}{{160.1}{429}{Baselining Your Project}{section.160.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {160.2}Managing Artifact and Dependency Versions}{430}{section.160.2}\protected@file@percent }
\newlabel{managing-artifact-and-dependency-versions}{{160.2}{430}{Managing Artifact and Dependency Versions}{section.160.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {160.3}Related Topics}{431}{section.160.3}\protected@file@percent }
\newlabel{related-topics-6}{{160.3}{431}{Related Topics}{section.160.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {161}Deploying WARs (WAB Generator)}{433}{chapter.161}\protected@file@percent }
\newlabel{deploying-wars-wab-generator}{{161}{433}{Deploying WARs (WAB Generator)}{chapter.161}{}}
\@writefile{toc}{\contentsline {section}{\numberline {161.1}WAR versus WAB Structure}{434}{section.161.1}\protected@file@percent }
\newlabel{war-versus-wab-structure}{{161.1}{434}{WAR versus WAB Structure}{section.161.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {161.2}Deploying a WAR}{435}{section.161.2}\protected@file@percent }
\newlabel{deploying-a-war}{{161.2}{435}{Deploying a WAR}{section.161.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {161.3}Saving a Copy of the WAB}{435}{section.161.3}\protected@file@percent }
\newlabel{saving-a-copy-of-the-wab}{{161.3}{435}{Saving a Copy of the WAB}{section.161.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {161.4}Related Topics}{435}{section.161.4}\protected@file@percent }
\newlabel{related-topics-7}{{161.4}{435}{Related Topics}{section.161.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {162}Architecture}{437}{chapter.162}\protected@file@percent }
\newlabel{architecture}{{162}{437}{Architecture}{chapter.162}{}}
\@writefile{toc}{\contentsline {section}{\numberline {162.1}Core}{437}{section.162.1}\protected@file@percent }
\newlabel{core}{{162.1}{437}{Core}{section.162.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {162.1}{\ignorespaces Liferay DXP portals and Sites contain content and widgets. Liferay DXP can also be used ``headless''---without the UI.}}{438}{figure.162.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {162.2}{\ignorespaces The Core provides a runtime environment for components, such as the ones here. New component implementations can extend or replace existing implementations dynamically.}}{439}{figure.162.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {162.2}Services}{439}{section.162.2}\protected@file@percent }
\newlabel{services}{{162.2}{439}{Services}{section.162.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {162.3}{\ignorespaces Remote and Liferay DXP applications can invoke services via REST web APIs. Liferay DXP Java-based portlets can also invoke services via Java APIs.}}{440}{figure.162.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {162.3}UI}{440}{section.162.3}\protected@file@percent }
\newlabel{ui}{{162.3}{440}{UI}{section.162.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {163}Liferay Portal Classloader Hierarchy}{443}{chapter.163}\protected@file@percent }
\newlabel{liferay-portal-classloader-hierarchy}{{163}{443}{Liferay Portal Classloader Hierarchy}{chapter.163}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {163.1}{\ignorespaces 0: Here is Liferay's classloader hierarchy.}}{444}{figure.163.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {163.1}Web Application Classloading Perspective}{445}{section.163.1}\protected@file@percent }
\newlabel{web-application-classloading-perspective}{{163.1}{445}{Web Application Classloading Perspective}{section.163.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {163.2}Other Classloading Perspectives}{445}{section.163.2}\protected@file@percent }
\newlabel{other-classloading-perspectives}{{163.2}{445}{Other Classloading Perspectives}{section.163.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {163.3}Related Topics}{445}{section.163.3}\protected@file@percent }
\newlabel{related-topics-8}{{163.3}{445}{Related Topics}{section.163.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {164}Liferay DXP Startup Phases}{447}{chapter.164}\protected@file@percent }
\newlabel{liferay-dxp-startup-phases}{{164}{447}{Liferay DXP Startup Phases}{chapter.164}{}}
\@writefile{toc}{\contentsline {section}{\numberline {164.1}Portal Context Initialization Phase}{447}{section.164.1}\protected@file@percent }
\newlabel{portal-context-initialization-phase}{{164.1}{447}{Portal Context Initialization Phase}{section.164.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {164.2}Main Servlet Initialization Phase}{448}{section.164.2}\protected@file@percent }
\newlabel{main-servlet-initialization-phase}{{164.2}{448}{Main Servlet Initialization Phase}{section.164.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {164.3}Acting on Events}{448}{section.164.3}\protected@file@percent }
\newlabel{acting-on-events}{{164.3}{448}{Acting on Events}{section.164.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {164.4}ModuleServiceLifecycle Events}{448}{section.164.4}\protected@file@percent }
\newlabel{moduleservicelifecycle-events}{{164.4}{448}{ModuleServiceLifecycle Events}{section.164.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {164.5}Portal Startup Events}{448}{section.164.5}\protected@file@percent }
\newlabel{portal-startup-events}{{164.5}{448}{Portal Startup Events}{section.164.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {164.6}Related Topics}{449}{section.164.6}\protected@file@percent }
\newlabel{related-topics-9}{{164.6}{449}{Related Topics}{section.164.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {165}The Benefits of Modularity}{451}{chapter.165}\protected@file@percent }
\newlabel{the-benefits-of-modularity}{{165}{451}{The Benefits of Modularity}{chapter.165}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {165.1}{\ignorespaces The Apollo spacecraft's modules collectively took astronauts to the moon's surface and back to Earth.}}{451}{figure.165.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {165.1}Modularity Benefits for Software}{452}{section.165.1}\protected@file@percent }
\newlabel{modularity-benefits-for-software}{{165.1}{452}{Modularity Benefits for Software}{section.165.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {165.2}Distinct Functionality}{452}{section.165.2}\protected@file@percent }
\newlabel{distinct-functionality}{{165.2}{452}{Distinct Functionality}{section.165.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {165.3}Encapsulation}{452}{section.165.3}\protected@file@percent }
\newlabel{encapsulation}{{165.3}{452}{Encapsulation}{section.165.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {165.4}Dependencies}{453}{section.165.4}\protected@file@percent }
\newlabel{dependencies}{{165.4}{453}{Dependencies}{section.165.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {165.5}Reusability}{453}{section.165.5}\protected@file@percent }
\newlabel{reusability}{{165.5}{453}{Reusability}{section.165.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {165.6}Example: Designing a Modular Application}{453}{section.165.6}\protected@file@percent }
\newlabel{example-designing-a-modular-application}{{165.6}{453}{Example: Designing a Modular Application}{section.165.6}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {165.2}{\ignorespaces The speech recognition application can be implemented in a single monolithic code base or in modules, each focused on a particular function.}}{454}{figure.165.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {165.3}{\ignorespaces The \emph {Instruction manager} and \emph {Computer voice} modules designed for the speech recognition app can be used (or \emph {reused}) by a navigation app.}}{454}{figure.165.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {166}OSGi and Modularity}{455}{chapter.166}\protected@file@percent }
\newlabel{osgi-and-modularity}{{166}{455}{OSGi and Modularity}{chapter.166}{}}
\@writefile{toc}{\contentsline {section}{\numberline {166.1}Modules}{455}{section.166.1}\protected@file@percent }
\newlabel{modules}{{166.1}{455}{Modules}{section.166.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {166.2}API}{456}{section.166.2}\protected@file@percent }
\newlabel{api}{{166.2}{456}{API}{section.166.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {166.3}Provider}{457}{section.166.3}\protected@file@percent }
\newlabel{provider}{{166.3}{457}{Provider}{section.166.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {166.4}Consumer}{458}{section.166.4}\protected@file@percent }
\newlabel{consumer}{{166.4}{458}{Consumer}{section.166.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {166.5}A Typical Liferay Application}{460}{section.166.5}\protected@file@percent }
\newlabel{a-typical-liferay-application}{{166.5}{460}{A Typical Liferay Application}{section.166.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {166.6}Related Topics}{460}{section.166.6}\protected@file@percent }
\newlabel{related-topics-10}{{166.6}{460}{Related Topics}{section.166.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {167}Module Lifecycle}{461}{chapter.167}\protected@file@percent }
\newlabel{module-lifecycle}{{167}{461}{Module Lifecycle}{chapter.167}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {167.1}{\ignorespaces This state diagram illustrates the module lifecycle.}}{462}{figure.167.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {167.1}Related Topics}{463}{section.167.1}\protected@file@percent }
\newlabel{related-topics-11}{{167.1}{463}{Related Topics}{section.167.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {168}UI Architecture}{465}{chapter.168}\protected@file@percent }
\newlabel{ui-architecture}{{168}{465}{UI Architecture}{chapter.168}{}}
\@writefile{toc}{\contentsline {section}{\numberline {168.1}Content}{465}{section.168.1}\protected@file@percent }
\newlabel{content}{{168.1}{465}{Content}{section.168.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {168.2}Applications}{465}{section.168.2}\protected@file@percent }
\newlabel{applications}{{168.2}{465}{Applications}{section.168.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {168.1}{\ignorespaces Widget pages offer users functionality. Widgets are organized into a page template's rows and columns. This template has two columns: a smaller left column and larger right column. On this page, users select tags in the Tag Cloud widget and the matching tagged images show the Asset Publisher widget.}}{466}{figure.168.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {168.3}Themes}{466}{section.168.3}\protected@file@percent }
\newlabel{themes}{{168.3}{466}{Themes}{section.168.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {168.2}{\ignorespaces You can select an attractive theme and apply it to your site.}}{467}{figure.168.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {168.3}{\ignorespaces You can provide custom styling using the theme's \texttt {\_custom.sccs} file.}}{468}{figure.168.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {168.4}Product Navigation Sidebars and Panels}{469}{section.168.4}\protected@file@percent }
\newlabel{product-navigation\noindent -and-panels}{{168.4}{469}{Product Navigation Sidebars and Panels}{section.168.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {168.4}{\ignorespaces Liferay facilitates integrating custom administrative functionality through navigation menus and administrative applications.}}{469}{figure.168.4}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {169}Theme Components}{471}{chapter.169}\protected@file@percent }
\newlabel{theme-components}{{169}{471}{Theme Components}{chapter.169}{}}
\@writefile{toc}{\contentsline {section}{\numberline {169.1}Theme Templates and Utilities}{471}{section.169.1}\protected@file@percent }
\newlabel{theme-templates-and-utilities}{{169.1}{471}{Theme Templates and Utilities}{section.169.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {169.1}{\ignorespaces The collapsed navbar provides simplified user-friendly navigation for mobile devices.}}{472}{figure.169.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {169.2}{\ignorespaces Each theme template provides a portion of the page's markup and functionality.}}{472}{figure.169.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {169.2}CSS Frameworks and Extensions}{473}{section.169.2}\protected@file@percent }
\newlabel{css-frameworks-and-extensions}{{169.2}{473}{CSS Frameworks and Extensions}{section.169.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {169.3}Theme Customizations and Extensions}{473}{section.169.3}\protected@file@percent }
\newlabel{theme-customizations-and-extensions}{{169.3}{473}{Theme Customizations and Extensions}{section.169.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {169.4}Portlet Customizations and Extensions}{473}{section.169.4}\protected@file@percent }
\newlabel{portlet-customizations-and-extensions}{{169.4}{473}{Portlet Customizations and Extensions}{section.169.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {169.3}{\ignorespaces There are several extension points for customizing portlets}}{474}{figure.169.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {169.5}Related Topics}{474}{section.169.5}\protected@file@percent }
\newlabel{related-topics-12}{{169.5}{474}{Related Topics}{section.169.5}{}}
\gdef \LT@vii {\LT@entry
{1}{154.56912pt}\LT@entry
{1}{160.56912pt}\LT@entry
{1}{154.56912pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {170}Understanding the Page Structure}{475}{chapter.170}\protected@file@percent }
\newlabel{understanding-the-page-structure}{{170}{475}{Understanding the Page Structure}{chapter.170}{}}
\@writefile{toc}{\contentsline {section}{\numberline {170.1}Portlets or Fragments}{475}{section.170.1}\protected@file@percent }
\newlabel{portlets-or-fragments}{{170.1}{475}{Portlets or Fragments}{section.170.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {170.1}{\ignorespaces The page layout is broken into three key sections.}}{476}{figure.170.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {170.2}Layout Templates, Page Templates, and Site Templates}{476}{section.170.2}\protected@file@percent }
\newlabel{layout-templates-page-templates-and-site-templates}{{170.2}{476}{Layout Templates, Page Templates, and Site Templates}{section.170.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {170.2}{\ignorespaces Each section of the page has elements and IDs that you can target for styling.}}{477}{figure.170.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {170.3}Product Navigation Sidebars and Panels}{478}{section.170.3}\protected@file@percent }
\newlabel{product-navigation-sidebars-and-panels-1}{{170.3}{478}{Product Navigation Sidebars and Panels}{section.170.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {170.3}{\ignorespaces Remember to account for the product navigation sidebars and panels when styling your site.}}{478}{figure.170.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {170.4}{\ignorespaces The Add Menu pushes the main contents to the left.}}{479}{figure.170.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {170.5}{\ignorespaces The Product Menu pushes the main contents to the right.}}{479}{figure.170.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {170.6}{\ignorespaces The Simulation Panel pushes the main contents to the left.}}{480}{figure.170.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {170.4}Related Topics}{480}{section.170.4}\protected@file@percent }
\newlabel{related-topics-13}{{170.4}{480}{Related Topics}{section.170.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {171}Bundle Classloading Flow}{481}{chapter.171}\protected@file@percent }
\newlabel{bundle-classloading-flow}{{171}{481}{Bundle Classloading Flow}{chapter.171}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {171.1}{\ignorespaces This flow chart illustrates classloading in a bundle.}}{482}{figure.171.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {172}Finding Extension Points}{483}{chapter.172}\protected@file@percent }
\newlabel{finding-extension-points}{{172}{483}{Finding Extension Points}{chapter.172}{}}
\@writefile{toc}{\contentsline {section}{\numberline {172.1}Locate the Related Module and Component}{483}{section.172.1}\protected@file@percent }
\newlabel{locate-the-related-module-and-component}{{172.1}{483}{Locate the Related Module and Component}{section.172.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {172.1}{\ignorespaces The module name can be found using the App Manager.}}{484}{figure.172.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {172.2}Finding Extension Points in a Component}{484}{section.172.2}\protected@file@percent }
\newlabel{finding-extension-points-in-a-component}{{172.2}{484}{Finding Extension Points in a Component}{section.172.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {172.2}{\ignorespaces The component name can be found using the App Manager.}}{485}{figure.172.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {173}Troubleshooting Customizations}{487}{chapter.173}\protected@file@percent }
\newlabel{troubleshooting-customizations}{{173}{487}{Troubleshooting Customizations}{chapter.173}{}}
\newlabel{cacheable-web-content-taglibs}{{173}{487}{Troubleshooting Customizations}{section*.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {174}Why doesn't the package I use from the fragment host resolve?}{489}{chapter.174}\protected@file@percent }
\newlabel{why-doesnt-the-package-i-use-from-the-fragment-host-resolve}{{174}{489}{Why doesn't the package I use from the fragment host resolve?}{chapter.174}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {175}Why Aren't JSP overrides I Made Using Fragments Showing?}{491}{chapter.175}\protected@file@percent }
\newlabel{why-arent-jsp-overrides-i-made-using-fragments-showing}{{175}{491}{Why Aren't JSP overrides I Made Using Fragments Showing?}{chapter.175}{}}
\@writefile{toc}{\contentsline {section}{\numberline {175.1}Related Topics}{491}{section.175.1}\protected@file@percent }
\newlabel{related-topics-14}{{175.1}{491}{Related Topics}{section.175.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {176}Using OSGi Services from EXT Plugins}{493}{chapter.176}\protected@file@percent }
\newlabel{using-osgi-services-from-ext-plugins}{{176}{493}{Using OSGi Services from EXT Plugins}{chapter.176}{}}
\@writefile{toc}{\contentsline {section}{\numberline {176.1}Related Topics}{493}{section.176.1}\protected@file@percent }
\newlabel{related-topics-15}{{176.1}{493}{Related Topics}{section.176.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {177}Contributing to Liferay Portal}{495}{chapter.177}\protected@file@percent }
\newlabel{contributing-to-liferay-portal}{{177}{495}{Contributing to Liferay Portal}{chapter.177}{}}
\@writefile{toc}{\contentsline {section}{\numberline {177.1}Building Liferay Portal from source}{495}{section.177.1}\protected@file@percent }
\newlabel{building-liferay-portal-from-source}{{177.1}{495}{Building Liferay Portal from source}{section.177.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {177.2}Tooling}{495}{section.177.2}\protected@file@percent }
\newlabel{tooling}{{177.2}{495}{Tooling}{section.177.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {177.3}Additional Resources}{496}{section.177.3}\protected@file@percent }
\newlabel{additional-resources}{{177.3}{496}{Additional Resources}{section.177.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {178}Model Listeners}{497}{chapter.178}\protected@file@percent }
\newlabel{model-listeners}{{178}{497}{Model Listeners}{chapter.178}{}}
\@writefile{toc}{\contentsline {section}{\numberline {178.1}Creating a Model Listener Class}{498}{section.178.1}\protected@file@percent }
\newlabel{creating-a-model-listener-class}{{178.1}{498}{Creating a Model Listener Class}{section.178.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {178.2}Register the Model Listener Service}{498}{section.178.2}\protected@file@percent }
\newlabel{register-the-model-listener-service}{{178.2}{498}{Register the Model Listener Service}{section.178.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {178.3}Listening For Persistence Events}{498}{section.178.3}\protected@file@percent }
\newlabel{listening-for-persistence-events}{{178.3}{498}{Listening For Persistence Events}{section.178.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {178.4}Related Topics}{499}{section.178.4}\protected@file@percent }
\newlabel{related-topics-16}{{178.4}{499}{Related Topics}{section.178.4}{}}
\gdef \LT@viii {\LT@entry
{1}{133.35468pt}\LT@entry
{1}{171.17975pt}\LT@entry
{1}{165.17975pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {179}Customizing JSPs}{501}{chapter.179}\protected@file@percent }
\newlabel{customizing-jsps}{{179}{501}{Customizing JSPs}{chapter.179}{}}
\@writefile{toc}{\contentsline {section}{\numberline {179.1}Using Liferay DXP's API to Override a JSP}{501}{section.179.1}\protected@file@percent }
\newlabel{using-liferay-dxps-api-to-override-a-jsp}{{179.1}{501}{Using Liferay DXP's API to Override a JSP}{section.179.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {179.2}Overriding a JSP Without Using Liferay DXP's API}{501}{section.179.2}\protected@file@percent }
\newlabel{overriding-a-jsp-without-using-liferay-dxps-api}{{179.2}{501}{Overriding a JSP Without Using Liferay DXP's API}{section.179.2}{}}
\gdef \LT@ix {\LT@entry
{1}{133.35468pt}\LT@entry
{1}{171.17975pt}\LT@entry
{1}{165.17975pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {180}Customizing JSPs with Dynamic Includes}{503}{chapter.180}\protected@file@percent }
\newlabel{customizing-jsps-with-dynamic-includes}{{180}{503}{Customizing JSPs with Dynamic Includes}{chapter.180}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {181}JSP Overrides Using Portlet Filters}{507}{chapter.181}\protected@file@percent }
\newlabel{jsp-overrides-using-portlet-filters}{{181}{507}{JSP Overrides Using Portlet Filters}{chapter.181}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {182}JSP Overrides Using OSGi Fragments}{511}{chapter.182}\protected@file@percent }
\newlabel{jsp-overrides-using-osgi-fragments}{{182}{511}{JSP Overrides Using OSGi Fragments}{chapter.182}{}}
\@writefile{toc}{\contentsline {section}{\numberline {182.1}Declaring a Fragment Host}{511}{section.182.1}\protected@file@percent }
\newlabel{declaring-a-fragment-host}{{182.1}{511}{Declaring a Fragment Host}{section.182.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {182.2}Provide the Overridden JSP}{512}{section.182.2}\protected@file@percent }
\newlabel{provide-the-overridden-jsp}{{182.2}{512}{Provide the Overridden JSP}{section.182.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {182.3}Using Fragment Host Internal Packages}{513}{section.182.3}\protected@file@percent }
\newlabel{using-fragment-host-internal-packages}{{182.3}{513}{Using Fragment Host Internal Packages}{section.182.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {182.4}Related Topics}{514}{section.182.4}\protected@file@percent }
\newlabel{related-topics-17}{{182.4}{514}{Related Topics}{section.182.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {183}JSP Overrides Using Custom JSP Bag}{515}{chapter.183}\protected@file@percent }
\newlabel{jsp-overrides-using-custom-jsp-bag}{{183}{515}{JSP Overrides Using Custom JSP Bag}{chapter.183}{}}
\@writefile{toc}{\contentsline {section}{\numberline {183.1}Providing a Custom JSP}{515}{section.183.1}\protected@file@percent }
\newlabel{providing-a-custom-jsp}{{183.1}{515}{Providing a Custom JSP}{section.183.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {183.2}Implement a Custom JSP Bag}{516}{section.183.2}\protected@file@percent }
\newlabel{implement-a-custom-jsp-bag}{{183.2}{516}{Implement a Custom JSP Bag}{section.183.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {183.3}Extend a JSP}{518}{section.183.3}\protected@file@percent }
\newlabel{extend-a-jsp}{{183.3}{518}{Extend a JSP}{section.183.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {183.4}Site Scoped JSP Customization}{518}{section.183.4}\protected@file@percent }
\newlabel{site-scoped-jsp-customization}{{183.4}{518}{Site Scoped JSP Customization}{section.183.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {183.5}Related Topics}{518}{section.183.5}\protected@file@percent }
\newlabel{related-topics-18}{{183.5}{518}{Related Topics}{section.183.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {184}Overriding Inline Content Using JSPs}{519}{chapter.184}\protected@file@percent }
\newlabel{overriding-inline-content-using-jsps}{{184}{519}{Overriding Inline Content Using JSPs}{chapter.184}{}}
\@writefile{toc}{\contentsline {section}{\numberline {184.1}Example: Overriding the fieldset Taglib Tag}{520}{section.184.1}\protected@file@percent }
\newlabel{example-overriding-the-fieldset-taglib-tag}{{184.1}{520}{Example: Overriding the fieldset Taglib Tag}{section.184.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {184.1}{\ignorespaces Liferay DXP's home page's search and sign in components are in a \texttt {fieldset}.}}{522}{figure.184.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {184.2}{\ignorespaces Before the \texttt {fieldset}'s nested fields, it prints \emph {test} surrounded by asterisks.}}{523}{figure.184.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {184.2}Related Topics}{523}{section.184.2}\protected@file@percent }
\newlabel{related-topics-19}{{184.2}{523}{Related Topics}{section.184.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {185}Customizing Widgets}{525}{chapter.185}\protected@file@percent }
\newlabel{customizing-widgets}{{185}{525}{Customizing Widgets}{chapter.185}{}}
\@writefile{toc}{\contentsline {section}{\numberline {185.1}Implementing the TemplateHandler Interface}{526}{section.185.1}\protected@file@percent }
\newlabel{implementing-the-templatehandler-interface}{{185.1}{526}{Implementing the TemplateHandler Interface}{section.185.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {185.2}Defining Display Template Definitions}{526}{section.185.2}\protected@file@percent }
\newlabel{defining-display-template-definitions}{{185.2}{526}{Defining Display Template Definitions}{section.185.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {185.3}Defining Permissions}{526}{section.185.3}\protected@file@percent }
\newlabel{defining-permissions}{{185.3}{526}{Defining Permissions}{section.185.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {185.4}Exposing the Widget Template Selection}{526}{section.185.4}\protected@file@percent }
\newlabel{exposing-the-widget-template-selection}{{185.4}{526}{Exposing the Widget Template Selection}{section.185.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {185.5}Recommendations for Using Widget Templates}{527}{section.185.5}\protected@file@percent }
\newlabel{recommendations-for-using-widget-templates}{{185.5}{527}{Recommendations for Using Widget Templates}{section.185.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {186}Implementing Widget Templates}{529}{chapter.186}\protected@file@percent }
\newlabel{implementing-widget-templates}{{186}{529}{Implementing Widget Templates}{chapter.186}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {186.1}{\ignorespaces By using a custom display template, your portlet's display can be customized.}}{529}{figure.186.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {186.2}{\ignorespaces You can click a variable to add it to the template editor.}}{531}{figure.186.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {186.3}{\ignorespaces You can choose the Widget Template you want to apply from the widget's Configuration menu.}}{532}{figure.186.3}\protected@file@percent }
\gdef \LT@x {\LT@entry
{1}{192.48683pt}\LT@entry
{1}{277.26817pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {187}Dynamic Includes}{537}{chapter.187}\protected@file@percent }
\newlabel{dynamic-includes}{{187}{537}{Dynamic Includes}{chapter.187}{}}
\gdef \LT@xi {\LT@entry
{1}{59.00896pt}\LT@entry
{3}{83.90862pt}\LT@entry
{3}{99.2502pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {188}WYSIWYG Editor Dynamic Includes}{539}{chapter.188}\protected@file@percent }
\newlabel{wysiwyg-editor-dynamic-includes}{{188}{539}{WYSIWYG Editor Dynamic Includes}{chapter.188}{}}
\@writefile{toc}{\contentsline {section}{\numberline {188.1}Related Topics}{540}{section.188.1}\protected@file@percent }
\newlabel{related-topics-20}{{188.1}{540}{Related Topics}{section.188.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {189}Top Head JSP Dynamic Includes}{541}{chapter.189}\protected@file@percent }
\newlabel{top-head-jsp-dynamic-includes}{{189}{541}{Top Head JSP Dynamic Includes}{chapter.189}{}}
\@writefile{toc}{\contentsline {section}{\numberline {189.1}Related Topics}{542}{section.189.1}\protected@file@percent }
\newlabel{related-topics-21}{{189.1}{542}{Related Topics}{section.189.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {190}Top JS Dynamic Include}{543}{chapter.190}\protected@file@percent }
\newlabel{top-js-dynamic-include}{{190}{543}{Top JS Dynamic Include}{chapter.190}{}}
\@writefile{toc}{\contentsline {section}{\numberline {190.1}Related Topics}{544}{section.190.1}\protected@file@percent }
\newlabel{related-topics-22}{{190.1}{544}{Related Topics}{section.190.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {191}Bottom JSP Dynamic Includes}{545}{chapter.191}\protected@file@percent }
\newlabel{bottom-jsp-dynamic-includes}{{191}{545}{Bottom JSP Dynamic Includes}{chapter.191}{}}
\@writefile{toc}{\contentsline {section}{\numberline {191.1}Related Topics}{546}{section.191.1}\protected@file@percent }
\newlabel{related-topics-23}{{191.1}{546}{Related Topics}{section.191.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {192}Waiting on Lifecycle Events}{547}{chapter.192}\protected@file@percent }
\newlabel{waiting-on-lifecycle-events}{{192}{547}{Waiting on Lifecycle Events}{chapter.192}{}}
\@writefile{toc}{\contentsline {section}{\numberline {192.1}Taking action from a component}{547}{section.192.1}\protected@file@percent }
\newlabel{taking-action-from-a-component}{{192.1}{547}{Taking action from a component}{section.192.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {192.2}Taking action from a non-component class}{548}{section.192.2}\protected@file@percent }
\newlabel{taking-action-from-a-non-component-class}{{192.2}{548}{Taking action from a non-component class}{section.192.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {192.3}Related Topics}{549}{section.192.3}\protected@file@percent }
\newlabel{related-topics-24}{{192.3}{549}{Related Topics}{section.192.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {193}Liferay Forms}{551}{chapter.193}\protected@file@percent }
\newlabel{liferay-forms}{{193}{551}{Liferay Forms}{chapter.193}{}}
\@writefile{toc}{\contentsline {section}{\numberline {193.1}Liferay Forms Extension Points}{551}{section.193.1}\protected@file@percent }
\newlabel{liferay-forms-extension-points}{{193.1}{551}{Liferay Forms Extension Points}{section.193.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {194}Form Storage Adapters}{553}{chapter.194}\protected@file@percent }
\newlabel{form-storage-adapters}{{194}{553}{Form Storage Adapters}{chapter.194}{}}
\@writefile{toc}{\contentsline {section}{\numberline {194.1}Storage Adapter Methods}{553}{section.194.1}\protected@file@percent }
\newlabel{storage-adapter-methods}{{194.1}{553}{Storage Adapter Methods}{section.194.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {194.1}{\ignorespaces Choose a Storage Type for your form records.}}{554}{figure.194.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {194.2}The CRUD Methods}{554}{section.194.2}\protected@file@percent }
\newlabel{the-crud-methods}{{194.2}{554}{The CRUD Methods}{section.194.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {194.3}Validating Form Entries}{555}{section.194.3}\protected@file@percent }
\newlabel{validating-form-entries}{{194.3}{555}{Validating Form Entries}{section.194.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {194.4}Enabling the Storage Adapter}{556}{section.194.4}\protected@file@percent }
\newlabel{enabling-the-storage-adapter}{{194.4}{556}{Enabling the Storage Adapter}{section.194.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {195}Creating a Form Storage Adapter}{557}{chapter.195}\protected@file@percent }
\newlabel{creating-a-form-storage-adapter}{{195}{557}{Creating a Form Storage Adapter}{chapter.195}{}}
\@writefile{toc}{\contentsline {section}{\numberline {195.1}Storage Adapter CRUD Operations}{557}{section.195.1}\protected@file@percent }
\newlabel{storage-adapter-crud-operations}{{195.1}{557}{Storage Adapter CRUD Operations}{section.195.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {195.2}Create}{557}{section.195.2}\protected@file@percent }
\newlabel{create}{{195.2}{557}{Create}{section.195.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {195.3}Read}{558}{section.195.3}\protected@file@percent }
\newlabel{read}{{195.3}{558}{Read}{section.195.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {195.4}Update}{559}{section.195.4}\protected@file@percent }
\newlabel{update}{{195.4}{559}{Update}{section.195.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {195.5}Delete}{559}{section.195.5}\protected@file@percent }
\newlabel{delete}{{195.5}{559}{Delete}{section.195.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {195.6}Beyond CRUD: Validation}{560}{section.195.6}\protected@file@percent }
\newlabel{beyond-crud-validation}{{195.6}{560}{Beyond CRUD: Validation}{section.195.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {196}Overriding Language Keys}{561}{chapter.196}\protected@file@percent }
\newlabel{overriding-language-keys}{{196}{561}{Overriding Language Keys}{chapter.196}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {197}Overriding Global Language Keys}{563}{chapter.197}\protected@file@percent }
\newlabel{overriding-global-language-keys}{{197}{563}{Overriding Global Language Keys}{chapter.197}{}}
\@writefile{toc}{\contentsline {section}{\numberline {197.1}Determine the language keys to override}{563}{section.197.1}\protected@file@percent }
\newlabel{determine-the-language-keys-to-override}{{197.1}{563}{Determine the language keys to override}{section.197.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {197.1}{\ignorespaces Messages displayed in Liferay's user interface can be customized.}}{564}{figure.197.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {197.2}Override the keys in a new language properties file}{564}{section.197.2}\protected@file@percent }
\newlabel{override-the-keys-in-a-new-language-properties-file}{{197.2}{564}{Override the keys in a new language properties file}{section.197.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {197.3}Create a Resource Bundle service component}{564}{section.197.3}\protected@file@percent }
\newlabel{create-a-resource-bundle-service-component}{{197.3}{564}{Create a Resource Bundle service component}{section.197.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {197.2}{\ignorespaces This button uses the overridden \texttt {publish} key.}}{566}{figure.197.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {197.4}Related Topics}{566}{section.197.4}\protected@file@percent }
\newlabel{related-topics-25}{{197.4}{566}{Related Topics}{section.197.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {198}Overriding a Module's Language Keys}{567}{chapter.198}\protected@file@percent }
\newlabel{overriding-a-modules-language-keys}{{198}{567}{Overriding a Module's Language Keys}{chapter.198}{}}
\@writefile{toc}{\contentsline {section}{\numberline {198.1}Find the module and its metadata and language keys}{567}{section.198.1}\protected@file@percent }
\newlabel{find-the-module-and-its-metadata-and-language-keys}{{198.1}{567}{Find the module and its metadata and language keys}{section.198.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {198.2}Write custom language key values}{568}{section.198.2}\protected@file@percent }
\newlabel{write-custom-language-key-values}{{198.2}{568}{Write custom language key values}{section.198.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {198.3}Prioritize Your Module's Resource Bundle}{569}{section.198.3}\protected@file@percent }
\newlabel{prioritize-your-modules-resource-bundle}{{198.3}{569}{Prioritize Your Module's Resource Bundle}{section.198.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {198.4}Related Topics}{570}{section.198.4}\protected@file@percent }
\newlabel{related-topics-26}{{198.4}{570}{Related Topics}{section.198.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {199}Overriding Liferay Services (Service Wrappers)}{571}{chapter.199}\protected@file@percent }
\newlabel{overriding-liferay-services-service-wrappers}{{199}{571}{Overriding Liferay Services (Service Wrappers)}{chapter.199}{}}
\@writefile{toc}{\contentsline {section}{\numberline {199.1}Related Topics}{572}{section.199.1}\protected@file@percent }
\newlabel{related-topics-27}{{199.1}{572}{Related Topics}{section.199.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {200}Overriding lpkg Files}{573}{chapter.200}\protected@file@percent }
\newlabel{overriding-lpkg-files}{{200}{573}{Overriding lpkg Files}{chapter.200}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {201}Overriding Liferay MVC Commands}{575}{chapter.201}\protected@file@percent }
\newlabel{overriding-liferay-mvc-commands}{{201}{575}{Overriding Liferay MVC Commands}{chapter.201}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {202}Adding Logic to MVC Commands}{577}{chapter.202}\protected@file@percent }
\newlabel{adding-logic-to-mvc-commands}{{202}{577}{Adding Logic to MVC Commands}{chapter.202}{}}
\@writefile{toc}{\contentsline {section}{\numberline {202.1}Step 1: Implement the interface}{577}{section.202.1}\protected@file@percent }
\newlabel{step-1-implement-the-interface}{{202.1}{577}{Step 1: Implement the interface}{section.202.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {202.2}Step 2: Publish as a component}{578}{section.202.2}\protected@file@percent }
\newlabel{step-2-publish-as-a-component}{{202.2}{578}{Step 2: Publish as a component}{section.202.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {202.3}Step 3: Refer to the original implementation}{578}{section.202.3}\protected@file@percent }
\newlabel{step-3-refer-to-the-original-implementation}{{202.3}{578}{Step 3: Refer to the original implementation}{section.202.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {202.4}Step 4: Add the logic}{579}{section.202.4}\protected@file@percent }
\newlabel{step-4-add-the-logic}{{202.4}{579}{Step 4: Add the logic}{section.202.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {203}Overriding MVCRenderCommands}{581}{chapter.203}\protected@file@percent }
\newlabel{overriding-mvcrendercommands}{{203}{581}{Overriding MVCRenderCommands}{chapter.203}{}}
\@writefile{toc}{\contentsline {section}{\numberline {203.1}Adding Logic to an Existing MVC Render Command}{582}{section.203.1}\protected@file@percent }
\newlabel{adding-logic-to-an-existing-mvc-render-command}{{203.1}{582}{Adding Logic to an Existing MVC Render Command}{section.203.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {203.2}Redirecting to a New JSP}{582}{section.203.2}\protected@file@percent }
\newlabel{redirecting-to-a-new-jsp}{{203.2}{582}{Redirecting to a New JSP}{section.203.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {203.3}Related Topics}{584}{section.203.3}\protected@file@percent }
\newlabel{related-topics-28}{{203.3}{584}{Related Topics}{section.203.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {204}Overriding MVCActionCommands}{585}{chapter.204}\protected@file@percent }
\newlabel{overriding-mvcactioncommands}{{204}{585}{Overriding MVCActionCommands}{chapter.204}{}}
\@writefile{toc}{\contentsline {section}{\numberline {204.1}Related Topics}{586}{section.204.1}\protected@file@percent }
\newlabel{related-topics-29}{{204.1}{586}{Related Topics}{section.204.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {205}Overriding MVCResourceCommands}{587}{chapter.205}\protected@file@percent }
\newlabel{overriding-mvcresourcecommands}{{205}{587}{Overriding MVCResourceCommands}{chapter.205}{}}
\@writefile{toc}{\contentsline {section}{\numberline {205.1}Related Topics}{588}{section.205.1}\protected@file@percent }
\newlabel{related-topics-30}{{205.1}{588}{Related Topics}{section.205.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {206}Overriding OSGi Services}{589}{chapter.206}\protected@file@percent }
\newlabel{overriding-osgi-services}{{206}{589}{Overriding OSGi Services}{chapter.206}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {207}Examining an OSGi Service to Override}{591}{chapter.207}\protected@file@percent }
\newlabel{examining-an-osgi-service-to-override}{{207}{591}{Examining an OSGi Service to Override}{chapter.207}{}}
\@writefile{toc}{\contentsline {section}{\numberline {207.1}Gathering Information on a Service}{591}{section.207.1}\protected@file@percent }
\newlabel{gathering-information-on-a-service}{{207.1}{591}{Gathering Information on a Service}{section.207.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {207.2}Step 1: Copy the Service Interface Name}{592}{section.207.2}\protected@file@percent }
\newlabel{step-1-copy-the-service-interface-name}{{207.2}{592}{Step 1: Copy the Service Interface Name}{section.207.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {207.3}Step 2: Copy the Existing Service Name}{592}{section.207.3}\protected@file@percent }
\newlabel{step-2-copy-the-existing-service-name}{{207.3}{592}{Step 2: Copy the Existing Service Name}{section.207.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {207.4}Step 3: Gather Reference Configuration Details (if reconfiguration is needed)}{593}{section.207.4}\protected@file@percent }
\newlabel{step-3-gather-reference-configuration-details-if-reconfiguration-is-needed}{{207.4}{593}{Step 3: Gather Reference Configuration Details (if reconfiguration is needed)}{section.207.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {207.5}Related Topics}{594}{section.207.5}\protected@file@percent }
\newlabel{related-topics-31}{{207.5}{594}{Related Topics}{section.207.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {208}Creating a Custom OSGi Service}{595}{chapter.208}\protected@file@percent }
\newlabel{creating-a-custom-osgi-service}{{208}{595}{Creating a Custom OSGi Service}{chapter.208}{}}
\@writefile{toc}{\contentsline {section}{\numberline {208.1}Related Topics}{596}{section.208.1}\protected@file@percent }
\newlabel{related-topics-32}{{208.1}{596}{Related Topics}{section.208.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {209}Reconfiguring Components to Use Your OSGi Service}{597}{chapter.209}\protected@file@percent }
\newlabel{reconfiguring-components-to-use-your-osgi-service}{{209}{597}{Reconfiguring Components to Use Your OSGi Service}{chapter.209}{}}
\@writefile{toc}{\contentsline {section}{\numberline {209.1}Reconfiguring the Service Reference}{598}{section.209.1}\protected@file@percent }
\newlabel{reconfiguring-the-service-reference}{{209.1}{598}{Reconfiguring the Service Reference}{section.209.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {209.1}{\ignorespaces Because the example component's service reference is overridden by the configuration file deployment, the portlet indicates it's calling the custom service.}}{599}{figure.209.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {209.2}Related Topics}{599}{section.209.2}\protected@file@percent }
\newlabel{related-topics-33}{{209.2}{599}{Related Topics}{section.209.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {210}Portlet Filters}{601}{chapter.210}\protected@file@percent }
\newlabel{portlet-filters}{{210}{601}{Portlet Filters}{chapter.210}{}}
\@writefile{toc}{\contentsline {section}{\numberline {210.1}Sample Portlet}{602}{section.210.1}\protected@file@percent }
\newlabel{sample-portlet}{{210.1}{602}{Sample Portlet}{section.210.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {210.2}Render filter 1 hides parts of user email addresses}{602}{section.210.2}\protected@file@percent }
\newlabel{render-filter-1-hides-parts-of-user-email-addresses}{{210.2}{602}{Render filter 1 hides parts of user email addresses}{section.210.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {210.3}RenderFilter 2 Logs Statistics}{604}{section.210.3}\protected@file@percent }
\newlabel{renderfilter-2-logs-statistics}{{210.3}{604}{RenderFilter 2 Logs Statistics}{section.210.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {210.4}Related Topics}{605}{section.210.4}\protected@file@percent }
\newlabel{related-topics-34}{{210.4}{605}{Related Topics}{section.210.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {211}Product Navigation}{607}{chapter.211}\protected@file@percent }
\newlabel{product-navigation}{{211}{607}{Product Navigation}{chapter.211}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {211.1}{\ignorespaces The main product navigation menus include the Product Menu, Control Menu, Simulation Menu and User Personal Menu.}}{607}{figure.211.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {211.1}Product Menu}{608}{section.211.1}\protected@file@percent }
\newlabel{product-menu}{{211.1}{608}{Product Menu}{section.211.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {211.2}Control Menu}{608}{section.211.2}\protected@file@percent }
\newlabel{control-menu}{{211.2}{608}{Control Menu}{section.211.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {211.2}{\ignorespaces The Control Menu has three configurable areas: left, right, and middle. It also displays the title and type of page that you are currently viewing.}}{608}{figure.211.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {211.3}{\ignorespaces When switching your context to web content, the Control Menu adapts to provide helpful options for that area.}}{608}{figure.211.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {211.3}Simulation Menu}{609}{section.211.3}\protected@file@percent }
\newlabel{simulation-menu}{{211.3}{609}{Simulation Menu}{section.211.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {211.4}{\ignorespaces The Simulation Menu offers a device preview application.}}{609}{figure.211.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {211.4}User Personal Menu}{610}{section.211.4}\protected@file@percent }
\newlabel{user-personal-menu}{{211.4}{610}{User Personal Menu}{section.211.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {211.5}{\ignorespaces By default, the User Personal Menu contains the signed-in user's avatar, which opens the user's settings when selected.}}{610}{figure.211.5}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {212}Customizing the Product Menu}{613}{chapter.212}\protected@file@percent }
\newlabel{customizing-the-product-menu}{{212}{613}{Customizing the Product Menu}{chapter.212}{}}
\@writefile{toc}{\contentsline {section}{\numberline {212.1}PanelCategory Interface}{613}{section.212.1}\protected@file@percent }
\newlabel{panelcategory-interface}{{212.1}{613}{PanelCategory Interface}{section.212.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {212.2}BasePanelCategory}{614}{section.212.2}\protected@file@percent }
\newlabel{basepanelcategory}{{212.2}{614}{BasePanelCategory}{section.212.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {212.3}BaseJSPPanelCategory}{614}{section.212.3}\protected@file@percent }
\newlabel{basejsppanelcategory}{{212.3}{614}{BaseJSPPanelCategory}{section.212.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {212.4}PanelApp Interface}{614}{section.212.4}\protected@file@percent }
\newlabel{panelapp-interface}{{212.4}{614}{PanelApp Interface}{section.212.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {213}Adding Custom Panel Categories}{617}{chapter.213}\protected@file@percent }
\newlabel{adding-custom-panel-categories}{{213}{617}{Adding Custom Panel Categories}{chapter.213}{}}
\@writefile{toc}{\contentsline {section}{\numberline {213.1}Creating the OSGi Module}{617}{section.213.1}\protected@file@percent }
\newlabel{creating-the-osgi-module}{{213.1}{617}{Creating the OSGi Module}{section.213.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {213.2}Implementing Liferay's Frameworks}{617}{section.213.2}\protected@file@percent }
\newlabel{implementing-liferays-frameworks}{{213.2}{617}{Implementing Liferay's Frameworks}{section.213.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {214}Adding Custom Panel Apps}{621}{chapter.214}\protected@file@percent }
\newlabel{adding-custom-panel-apps}{{214}{621}{Adding Custom Panel Apps}{chapter.214}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {215}Customizing the Control Menu}{623}{chapter.215}\protected@file@percent }
\newlabel{customizing-the-control-menu}{{215}{623}{Customizing the Control Menu}{chapter.215}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {215.1}{\ignorespaces This image shows where your entry will reside depending on the category you select.}}{623}{figure.215.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {215.1}ProductNavigationControlMenuEntry Interface}{623}{section.215.1}\protected@file@percent }
\newlabel{productnavigationcontrolmenuentry-interface}{{215.1}{623}{ProductNavigationControlMenuEntry Interface}{section.215.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {215.2}BaseProductNavigationControlMenuEntry}{624}{section.215.2}\protected@file@percent }
\newlabel{baseproductnavigationcontrolmenuentry}{{215.2}{624}{BaseProductNavigationControlMenuEntry}{section.215.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {215.3}BaseJSPProductNavigationControlMenuEntry}{624}{section.215.3}\protected@file@percent }
\newlabel{basejspproductnavigationcontrolmenuentry}{{215.3}{624}{BaseJSPProductNavigationControlMenuEntry}{section.215.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {216}Creating Control Menu Entries}{625}{chapter.216}\protected@file@percent }
\newlabel{creating-control-menu-entries}{{216}{625}{Creating Control Menu Entries}{chapter.216}{}}
\@writefile{toc}{\contentsline {section}{\numberline {216.1}Creating the OSGi Module}{625}{section.216.1}\protected@file@percent }
\newlabel{creating-the-osgi-module-1}{{216.1}{625}{Creating the OSGi Module}{section.216.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {216.2}Implementing Liferay's Frameworks}{625}{section.216.2}\protected@file@percent }
\newlabel{implementing-liferays-frameworks-1}{{216.2}{625}{Implementing Liferay's Frameworks}{section.216.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {217}Defining Icons and Tooltips}{629}{chapter.217}\protected@file@percent }
\newlabel{defining-icons-and-tooltips}{{217}{629}{Defining Icons and Tooltips}{chapter.217}{}}
\@writefile{toc}{\contentsline {section}{\numberline {217.1}Control Menu Entry Icons}{629}{section.217.1}\protected@file@percent }
\newlabel{control-menu-entry-icons}{{217.1}{629}{Control Menu Entry Icons}{section.217.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {217.2}Control Menu Entry Tooltips}{630}{section.217.2}\protected@file@percent }
\newlabel{control-menu-entry-tooltips}{{217.2}{630}{Control Menu Entry Tooltips}{section.217.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {218}Extending the Simulation Menu}{631}{chapter.218}\protected@file@percent }
\newlabel{extending-the-simulation-menu}{{218}{631}{Extending the Simulation Menu}{chapter.218}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {218.1}{\ignorespaces The Simulation Menu also displays Segments to help simulate different user experiences.}}{632}{figure.218.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {219}Customizing the User Personal Bar and Menu}{635}{chapter.219}\protected@file@percent }
\newlabel{customizing-the-user-personal-bar-and-menu}{{219}{635}{Customizing the User Personal Bar and Menu}{chapter.219}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {219.1}{\ignorespaces By default, the User Personal Bar contains the signed-in user's avatar, which opens the Personal Menu when selected.}}{635}{figure.219.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {219.1}Displaying the Personal Menu}{636}{section.219.1}\protected@file@percent }
\newlabel{displaying-the-personal-menu}{{219.1}{636}{Displaying the Personal Menu}{section.219.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {220}Using a Custom Portlet in Place of the User Personal Bar}{637}{chapter.220}\protected@file@percent }
\newlabel{using-a-custom-portlet-in-place-of-the-user-personal-bar}{{220}{637}{Using a Custom Portlet in Place of the User Personal Bar}{chapter.220}{}}
\@writefile{toc}{\contentsline {section}{\numberline {220.1}Related Topics}{638}{section.220.1}\protected@file@percent }
\newlabel{related-topics-35}{{220.1}{638}{Related Topics}{section.220.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {221}Customizing the Personal Menu}{639}{chapter.221}\protected@file@percent }
\newlabel{customizing-the-personal-menu}{{221}{639}{Customizing the Personal Menu}{chapter.221}{}}
\@writefile{toc}{\contentsline {section}{\numberline {221.1}Adding an Entry to the Personal Menu}{639}{section.221.1}\protected@file@percent }
\newlabel{adding-an-entry-to-the-personal-menu}{{221.1}{639}{Adding an Entry to the Personal Menu}{section.221.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {221.1}{\ignorespaces The Personal Menu is organized into four sections.}}{640}{figure.221.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {221.2}Adding a Portlet Entry to the Personal Menu}{641}{section.221.2}\protected@file@percent }
\newlabel{adding-a-portlet-entry-to-the-personal-menu}{{221.2}{641}{Adding a Portlet Entry to the Personal Menu}{section.221.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {221.3}Related Topics}{642}{section.221.3}\protected@file@percent }
\newlabel{related-topics-36}{{221.3}{642}{Related Topics}{section.221.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {222}Customizing Workflow}{643}{chapter.222}\protected@file@percent }
\newlabel{customizing-workflow}{{222}{643}{Customizing Workflow}{chapter.222}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {223}Creating SLA Calendars}{645}{chapter.223}\protected@file@percent }
\newlabel{creating-sla-calendars}{{223}{645}{Creating SLA Calendars}{chapter.223}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {223.1}{\ignorespaces Write a Custom SLA Calendar if the default, 24/7 calendar isn't sufficient.}}{645}{figure.223.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {223.1}Dependencies}{646}{section.223.1}\protected@file@percent }
\newlabel{dependencies-1}{{223.1}{646}{Dependencies}{section.223.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {223.2}Implementation Steps}{646}{section.223.2}\protected@file@percent }
\newlabel{implementation-steps}{{223.2}{646}{Implementation Steps}{section.223.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {224}Customizing Core Functionality with Ext}{649}{chapter.224}\protected@file@percent }
\newlabel{customizing-core-functionality-with-ext}{{224}{649}{Customizing Core Functionality with Ext}{chapter.224}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {225}Extending Core Classes Using Spring with Ext Plugins}{651}{chapter.225}\protected@file@percent }
\newlabel{extending-core-classes-using-spring-with-ext-plugins}{{225}{651}{Extending Core Classes Using Spring with Ext Plugins}{chapter.225}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {226}Overriding Core Classes with Ext Plugins}{653}{chapter.226}\protected@file@percent }
\newlabel{overriding-core-classes-with-ext-plugins}{{226}{653}{Overriding Core Classes with Ext Plugins}{chapter.226}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {227}Adding to the web.xml with Ext Plugins}{655}{chapter.227}\protected@file@percent }
\newlabel{adding-to-the-web.xml-with-ext-plugins}{{227}{655}{Adding to the web.xml with Ext Plugins}{chapter.227}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {228}Modifying the web.xml with Ext Plugins}{657}{chapter.228}\protected@file@percent }
\newlabel{modifying-the-web.xml-with-ext-plugins}{{228}{657}{Modifying the web.xml with Ext Plugins}{chapter.228}{}}
\@setckpt{developer/customization}{
\setcounter{page}{659}
\setcounter{equation}{0}
\setcounter{enumi}{3}
\setcounter{enumii}{2}
\setcounter{enumiii}{3}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{@memmarkcntra}{-1}
\setcounter{storedpagenumber}{1}
\setcounter{book}{0}
\setcounter{part}{2}
\setcounter{chapter}{228}
\setcounter{section}{0}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{@ppsavesec}{0}
\setcounter{@ppsaveapp}{0}
\setcounter{vslineno}{0}
\setcounter{poemline}{0}
\setcounter{modulo@vs}{0}
\setcounter{memfvsline}{0}
\setcounter{verse}{0}
\setcounter{chrsinstr}{0}
\setcounter{poem}{0}
\setcounter{newflo@tctr}{4}
\setcounter{@contsubnum}{0}
\setcounter{section@level}{0}
\setcounter{maxsecnumdepth}{1}
\setcounter{sidefootnote}{0}
\setcounter{pagenote}{0}
\setcounter{pagenoteshadow}{0}
\setcounter{memfbvline}{0}
\setcounter{bvlinectr}{0}
\setcounter{cp@cntr}{0}
\setcounter{ism@mctr}{0}
\setcounter{xsm@mctr}{0}
\setcounter{csm@mctr}{0}
\setcounter{ksm@mctr}{0}
\setcounter{xksm@mctr}{0}
\setcounter{cksm@mctr}{0}
\setcounter{msm@mctr}{0}
\setcounter{xmsm@mctr}{0}
\setcounter{cmsm@mctr}{0}
\setcounter{bsm@mctr}{0}
\setcounter{workm@mctr}{0}
\setcounter{sheetsequence}{731}
\setcounter{lastsheet}{2851}
\setcounter{lastpage}{2779}
\setcounter{figure}{0}
\setcounter{lofdepth}{1}
\setcounter{table}{0}
\setcounter{lotdepth}{1}
\setcounter{Item}{837}
\setcounter{Hfootnote}{5}
\setcounter{bookmark@seq@number}{0}
\setcounter{memhycontfloat}{0}
\setcounter{Hpagenote}{0}
\setcounter{r@tfl@t}{0}
\setcounter{float@type}{4}
\setcounter{LT@tables}{11}
\setcounter{LT@chunks}{3}
\setcounter{parentequation}{0}
\setcounter{FancyVerbLine}{0}
}
================================================
FILE: book/developer/customization.tex
================================================
\chapter{Liferay Customization}\label{liferay-customization}
Liferay DXP is highly customizable. Its modular architecture contains
components you can extend and override dynamically. This section
explains Liferay DXP's architecture and customization fundamentals and
demonstrates overriding and extending Liferay DXP components and
applications using APIs.
\begin{itemize}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/fundamentals}{Fundamentals}
include understanding and configuring dependencies, packaging, and
deployment. Here you'll work with module JARs, plugin WARs,
components, and Java packages in Liferay DXP.
\item
\href{/docs/7-2/customization/-/knowledge_base/c/architecture}{Architecture}
dives deep into how Liferay DXP uses modularity and OSGi to provide
the core, application modules, component services, and extension
points. Learning the architecture helps you develop better
customizations fast, and it empowers you to build extension points
into your own applications.
\item
Built-in customization features, including
\href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Widget
Templates} and
\href{/docs/7-2/user/-/knowledge_base/u/web-experience-management}{Web
Experience Management} help you customize content and pages faster.
All this is done from within the Liferay DXP UI.
\item
Application customization articles (listed after the Architecture
articles) demonstrate modifying Liferay applications via their APIs
and extension points.
\end{itemize}
Start with
\href{/docs/7-2/customization/-/knowledge_base/c/fundamentals}{Fundamentals}.
\chapter{Fundamentals}\label{fundamentals}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
The fundamentals of developing on Liferay DXP and customizing it are
perhaps best learned in the context of projects. It's in projects that
you configure access to Liferay DXP's API, extend and override Liferay
DXP features, and package your software for deployment. Projects are
developed as WARs or OSGi JARs, but are all installed to Liferay's OSGi
framework as OSGi bundles. These bundles can depend on external Java
packages, share Java packages, and be manipulated at run time via Apache
Gogo Shell. The fundamentals are explained in the context of projects so
that you understand them in a practical sense and can apply them right
away. Here are the fundamental topics:
\begin{itemize}
\item
\textbf{WARs Versus OSGi JAR} explains fundamental differences between
the WAR and OSGi JAR structures and how they're deployed in Liferay
DXP.
\item
\textbf{Configuring Dependencies} demonstrates how to identify and
configure Liferay artifacts and third-party artifacts to use their
Java packages in your projects.
\item
\textbf{Importing and Exporting Packages} shows how to import the
packages your projects need and export packages your projects provide.
Liferay's tooling detects package use and specifies package imports
automatically.
\item
\textbf{Semantic Versioning} shows how Liferay DXP uses a standard for
ascribing meaning to major, minor, and micro versions of modules and
Java packages.
\item
\textbf{Deploying WARs (WAB Generator)} explains how Liferay's WAB
Generator deploys WAR applications as OSGi Web Application Bundles
(WABs).
\item
\textbf{Gogo Shell} enables you to examine components, debug issues,
and manage deployments.
\end{itemize}
Start with understanding how WAR and OSGi JAR project structures are
used in development.
\chapter{Configuring Dependencies}\label{configuring-dependencies}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay DXP's modular environment lets modules provide and consume
capabilities via Java packages. To leverage packages from other modules
or traditional libraries in your project, you must configure them as
dependencies. Here you'll learn how to find artifacts (modules or
libraries) and configure dependencies on them.
\begin{itemize}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts}{Finding
Artifacts} explains how to use the Application Manager, Gogo Shell,
and Liferay DXP reference documentation to find artifacts deployed on
Liferay DXP and available in repositories.
\item
\href{/docs/7-2/customization/-/knowledge_base/c/specifying-dependencies}{Specifying
Dependencies} demonstrates specifying artifacts to Maven and Gradle
build frameworks. It shows you how to determine whether Liferay DXP
already exports packages from an artifact and how to configure such
artifacts as compile-time dependencies.
\item
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Resolving
Third-Party Library Package Dependencies} provides a workflow for
using packages that are only available in traditional library JARs
(JARs that aren't OSGi modules). It involves minimizing transitive
dependencies so you can resolve dependencies quicker and prevent
bloating your project with unnecessary JARs.
\end{itemize}
Your first step is to find the artifacts you need.
\chapter{Finding Artifacts}\label{finding-artifacts}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Using external artifacts in your project requires configuring their
dependencies. To do this, look up the artifact's attributes and plug
them into dependency entries for your build system (either
\href{https://gradle.org/}{Gradle} or
\href{https://maven.apache.org/}{Maven}). Your build system downloads
the dependency artifacts your project needs to compile successfully.
Before specifying an artifact as a dependency, you must first find its
attributes. Artifacts have these attributes:
\begin{itemize}
\tightlist
\item
\emph{Group ID}: Authoring organization
\item
\emph{Artifact ID}: Name/identifier
\item
\emph{Version}: Release number
\end{itemize}
Here you'll learn how to find artifact attributes to specify artifact
dependencies.
\section{Finding Core Artifact
Attributes}\label{finding-core-artifact-attributes}
Each Liferay artifact is a JAR file whose \texttt{META-INF/MANIFEST.MF}
file specifies OSGi bundle metadata the artifact's attributes. For
example, these two OSGi headers specify the artifact ID and version:
\begin{verbatim}
Bundle-SymbolicName: [artifact ID]
Bundle-Version: [version]
\end{verbatim}
\noindent\hrulefill
\textbf{Important:} Artifacts in Liferay DXP fix packs override Liferay
DXP installation artifacts. Subfolders of a fix pack ZIP file's
\texttt{binaries} folder hold the artifacts. If an installed fix pack
provides an artifact you depend \textbar{} on, specify the version of
that fix pack artifact in your dependency.
\noindent\hrulefill
This table lists each core Liferay DXP artifact's group ID and artifact
ID and where to find the artifact's manifest, which lists the artifact
version:
\emph{Core Liferay DXP Artifacts}:
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 8\tabcolsep) * \real{0.2364}}
>{\raggedright\arraybackslash}p{(\columnwidth - 8\tabcolsep) * \real{0.2909}}
>{\raggedright\arraybackslash}p{(\columnwidth - 8\tabcolsep) * \real{0.1636}}
>{\raggedright\arraybackslash}p{(\columnwidth - 8\tabcolsep) * \real{0.1818}}
>{\raggedright\arraybackslash}p{(\columnwidth - 8\tabcolsep) * \real{0.1273}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
File
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Group ID
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Artifact ID
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Version
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Origin
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{portal-kernel.jar} & \texttt{com.liferay.portal} &
\texttt{com.liferay.\
portal.kernel} & (see JAR's \texttt{MANIFEST.MF}) &
fix pack ZIP, Liferay DXP installation, or Liferay DXP dependencies
ZIP \\
\texttt{portal-impl.jar} & \texttt{com.liferay.portal} &
\texttt{com.liferay.\
portal.impl} & (see JAR's \texttt{MANIFEST.MF}) &
fix pack ZIP or Liferay DXP \texttt{.war} \\
\texttt{util-bridges.jar} & \texttt{com.liferay.portal} &
\texttt{com.liferay.\
util.bridges} & (see JAR's \texttt{MANIFEST.MF}) &
fix pack ZIP or Liferay DXP \texttt{.war} \\
\texttt{util-java.jar} & \texttt{com.liferay.portal} &
\texttt{com.liferay.\
util.java} & (see JAR's \texttt{MANIFEST.MF}) & fix
pack ZIP or Liferay DXP \texttt{.war} \\
\texttt{util-slf4j.jar} & \texttt{com.liferay.portal} &
\texttt{com.liferay.\
util.slf4j} & (see JAR's \texttt{MANIFEST.MF}) & fix
pack ZIP or Liferay DXP \texttt{.war} \\
\texttt{util-taglibs.jar} & \texttt{com.liferay.portal} &
\texttt{com.liferay.\
util.taglib} & (see JAR's \texttt{MANIFEST.MF}) &
fix pack ZIP or Liferay DXP \texttt{.war} \\
\texttt{com.liferay.*} JAR files & \texttt{com.liferay} & (see JAR's
\texttt{MANIFEST.MF}) & (see JAR's \texttt{MANIFEST.MF}) & fix pack ZIP,
Liferay DXP installation, Liferay DXP dependencies ZIP, or the OSGi
ZIP \\
\end{longtable}
Next, you'll learn how to find Liferay DXP app and independent module
artifact attributes.
\section{Finding Liferay App and Independent
Artifacts}\label{finding-liferay-app-and-independent-artifacts}
Independent modules and Liferay DXP app modules aren't part of the
Liferay DXP core. You must still, however, find their artifact
attributes if you depend on them. The resources below provide the
artifact details for Liferay DXP's apps and independent modules:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.3750}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.6250}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Resource
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Artifact Type
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\hyperref[app-manager]{App Manager} & Deployed modules \\
\hyperref[reference-docs]{Reference Docs} & Liferay DXP modules (per
release) \\
\hyperref[maven-central]{Maven Central} & All artifact types: Liferay
DXP and third party, module and non-module \\
\end{longtable}
\noindent\hrulefill
\noindent\hrulefill
\textbf{Important}: \texttt{com.liferay} is the group ID for all of
Liferay's apps and independent modules.
\noindent\hrulefill
The App Manager is the best source for information on deployed modules.
You'll learn about it next.
\section{App Manager}\label{app-manager}
\href{/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps\#using-the-app-manager}{The
App Manager} knows what's deployed on your Liferay instance. Use it to
find deployed module attributes.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In Liferay DXP, navigate to \emph{Control Panel} → \emph{Apps} →
\emph{App Manager}.
\item
Search for the module by its display name, symbolic name, or related
keywords. You can also browse for the module in its app. Whether
browsing or searching, the App Manager shows the module's artifact ID
and version number.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/configuring-dependencies-search-app-manager-for-module.png}
\caption{You can inspect deployed module artifact IDs and version
numbers.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/configuring-dependencies-indep-modules-in-app-manager.png}
\caption{The App Manager aggregates Liferay and independent modules.}
\end{figure}
If you don't know a deployed module's group ID, use the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix
Gogo Shell} to find it:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the Gogo Shell portlet in the Control Panel →
\emph{Configuration} → \emph{Gogo Shell}. Enter commands in the Felix
Gogo Shell command prompt.
\item
Search for the module by its display name (e.g.,
\texttt{Liferay\ Blogs\ API}) or a keyword. In the results, note the
module's number. You can use it in the next step. For example, Gogo
command results in the figure below show the Liferay Blogs API module
number.
\begin{figure}
\centering
\includegraphics{./images/configuring-deps-gogo-grep-for-module.png}
\caption{Results from this Gogo command show that the module's number
is \texttt{1173}.}
\end{figure}
\item
List the module's manifest headers by passing the module number to the
\texttt{headers} command. In the results, note the
\texttt{Bundle-Vendor} value: you'll match it with an artifact group
in a later step:
\begin{figure}
\centering
\includegraphics{./images/configuring-deps-gogo-module-info.png}
\caption{Results from running the \texttt{headers} command show the
module's bundle vendor and bundle version.}
\end{figure}
\item
On \href{https://search.maven.org/}{Maven Central} or
\href{https://mvnrepository.com}{MVNRepository}, search for the module
by its artifact ID.
\item
Determine the group ID by matching the \texttt{Bundle-Vendor} value
from step 3 with a group listed that provides the artifact.
\end{enumerate}
Next, Liferay DXP's reference documentation provides Liferay DXP app
artifact attributes.
\section{Reference Docs}\label{reference-docs}
Liferay DXP's app Javadoc lists each app module's artifact ID, version
number, and display name. This is the best place to look up Liferay DXP
app modules that aren't yet deployed to your Liferay DXP instance.
\noindent\hrulefill
\textbf{Note:} To find artifact information on a Core Liferay DXP
artifact, refer to the previous section \emph{Finding Core Liferay DXP
Artifact Attributes}.
\noindent\hrulefill
Follow these steps to find a Liferay DXP app module's attributes in the
Javadoc:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to Javadoc for an app module class. If you don't have a link
to the class's Javadoc, find it by browsing
{[}https://docs.liferay.com/dxp/apps{]}(
\item
Copy the class's package name.
\item
Navigate to the \emph{Overview} page.
\item
On the \emph{Overview} page, search for the package name you copied in
step 2.
\end{enumerate}
The heading above the package name shows the module's artifact ID,
version number, and display name. Remember, the group ID for all app
modules is \texttt{com.liferay}.
\begin{figure}
\centering
\includegraphics{./images/intro-configuring-dependencies-module-info-in-javadoc-overview.png}
\caption{Liferay DXP app Javadoc overviews list each app module's
display name, followed by its group ID, artifact ID, and version number
in a colon-separated string. It's a Gradle artifact syntax.}
\end{figure}
\noindent\hrulefill
\textbf{Note}: Module version numbers aren't currently included in any
tag library reference docs.
\noindent\hrulefill
Next, you'll learn how to look up artifacts on MVNRepository and Maven
Central.
\section{Maven Central}\label{maven-central}
Most artifacts, regardless of type or origin, are on
\href{https://mvnrepository.com/}{MVNRepository} and
\href{https://search.maven.org/}{Maven Central}. These sites can help
you find artifacts based on class packages. It's common to include an
artifact's ID in the start of an artifact's package names. For example,
if you depend on the class
\texttt{org.osgi.service.component.annotations.Component}, search for
the package name \texttt{org.osgi.service.component.annotations} on one
of the Maven sites.
\noindent\hrulefill
\textbf{Note:} Make sure to follow the instructions listed earlier to
determine the version of Liferay artifacts you need.
\noindent\hrulefill
Now that you know the artifact's attributes, you can configure a
dependency on it.
\section{Related Topics}\label{related-topics}
\href{/docs/7-2/customization/-/knowledge_base/c/specifying-dependencies}{Specifying
Dependencies}
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{Importing
Packages}
\href{/docs/7-2/customization/-/knowledge_base/c/exporting-packages}{Exporting
Packages}
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Resolving
Third Party Library Package Dependencies}
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Deploying
WARs (WAB Generator)}
\chapter{Specifying Dependencies}\label{specifying-dependencies}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Compiling your project and deploying it to Liferay DXP requires
satisfying its dependencies on external artifacts. After
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts}{finding
the attributes of an artifact}, set a dependency for it in your build
file. Here's how:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Determine whether Liferay DXP provides the Java packages you use from
the artifact. These files list the packages Liferay DXP exports:
\begin{itemize}
\item
\texttt{modules/core/portal-bootstrap/system.packages.extra.bnd}
file in the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/core/portal-bootstrap/system.packages.extra.bnd}{GitHub
repository}. It lists exported packages on separate lines, making
them easy to read.
\item
\texttt{META-INF/system.packages.extra.mf} file in
\texttt{{[}LIFERAY\_HOME{]}/osgi/core/com.liferay.portal.bootstrap.jar}.
The file is available in Liferay DXP bundles. It lists exported
packages in a paragraph wrapped at 70 columns--they're harder to
read here than in the \texttt{system.packages.extra.bnd} file.
\end{itemize}
\item
If Liferay DXP exports all the packages you use from the artifact,
specify the artifact as a compile-only dependency. This prevents your
build framework from bundling the artifact with your project. Here's
how to make the dependency compile-only:
\textbf{Gradle:} Add the \texttt{compileOnly} directive to the
dependency
\textbf{Maven:} Add the
\texttt{\textless{}scope\textgreater{}provided\textless{}/scope\textgreater{}}
element to the dependency.
\item
Add a dependency entry for the artifact. Here's the artifact
terminology for the Gradle and Maven build frameworks:
\end{enumerate}
\emph{Artifact Terminology}
\noindent\hrulefill
\begin{longtable}[]{@{}llll@{}}
\toprule\noalign{}
Framework & Group ID & Artifact ID & Version \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Gradle & \texttt{group} & \texttt{name} & \texttt{version} \\
Maven & \texttt{groupId} & \texttt{artifactId} & \texttt{version} \\
\end{longtable}
\noindent\hrulefill
Here is an example dependency on Liferay's Journal API module for
Gradle, and Maven:
\emph{Gradle (\texttt{build.gradle} entry):}
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay", name: "com.liferay.journal.api", version: "1.0.1"
...
}
\end{verbatim}
\emph{Maven (\texttt{pom.xml} entry):}
\begin{verbatim}
com.liferay
com.liferay.journal.api
1.0.1
provided
\end{verbatim}
\noindent\hrulefill
\textbf{Important:}
\href{/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports}{Liferay
DXP exports many third-party packages}. Deploy your module to check if
Liferay DXP or another module in your Liferay instance's OSGi runtime
framework provides the package you need. If it's provided already,
specify the corresponding dependency as being ``provided''. Here's how
to specify a provided dependency:
Maven:
\texttt{\textless{}scope\textgreater{}provided\textless{}/scope\textgreater{}}
Gradle: \texttt{providedCompile}
Don't deploy a provided package's JAR again or embed the JAR in your
project. Exporting the same package from different JARs leads to ``split
package'' issues, whose side affects differ from case to case. If the
package is in a third-party library (not an OSGi module), refer to
{[}Resolving Third
\noindent\hrulefill Party Library
Dependencies{]}(/docs/7-2/customization/-/knowledge\_base/c/adding-third-party-libraries-to-a-module).
\noindent\hrulefill
If you're developing a WAR that requires a different version of a
third-party package that
\href{/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports}{Liferay
DXP or another module exports}, specify that package in your
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{\texttt{Import-Package:}
list}. If the package provider is an OSGi module, publish its exported
packages by deploying that module. Otherwise, follow the instructions
for
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{adding
a third-party library (not an OSGi module)}.
\noindent\hrulefill
Nice! You know how to specify artifact dependencies. Now that's a skill
you can depend on!
\section{Related Topics}\label{related-topics-1}
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts}{Finding
Artifacts}
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{Importing
Packages}
\href{/docs/7-2/customization/-/knowledge_base/c/exporting-packages}{Exporting
Packages}
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Resolving
Third Party Library Package Dependencies}
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Deploying
WARs (WAB Generator)}
\chapter{Resolving Third Party Library Package
Dependencies}\label{resolving-third-party-library-package-dependencies}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay's OSGi framework lets you build applications composed of
multiple OSGi bundles (modules). For the framework to assemble the
modules into a working system, the modules must resolve their Java
package dependencies. In a perfect world, every Java library would be an
OSGi module, but many libraries aren't. So how do you resolve the
packages your project needs from non-OSGi third party libraries?
Here is the main workflow for resolving third party Java library
packages:
\textbf{Option 1 - Find an OSGi module of the library}: Projects, such
as \href{https://www.eclipse.org/orbit/}{Eclipse Orbit} and
\href{https://servicemix.apache.org/developers/source/bundles-source.html}{ServiceMix
Bundles}, convert hundreds of traditional Java libraries to OSGi
modules. Their artifacts are available at these locations:
\begin{itemize}
\tightlist
\item
\href{https://download.eclipse.org/tools/orbit/downloads/}{Eclipse
Orbit downloads (select a build)}
\item
\href{https://mvnrepository.com/artifact/org.apache.servicemix.bundles}{ServiceMix
Bundles}
\end{itemize}
Deploying the module to Liferay's OSGi framework lets you share it on
the system. If you find a module for the library you need,
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploy}
it. Then
\href{/docs/7-2/customization/-/knowledge_base/c/specifying-dependencies}{add
a compile-only dependency} for it in your project. When you deploy your
project, the OSGi framework wires the dependency module to your
project's module or web application bundle (WAB). If you don't find an
OSGi module based on the Java library, follow Option 2.
\noindent\hrulefill
\textbf{Tip:} Refrain from embedding library JARs that provide the same
\href{/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports}{packages
that Liferay DXP or existing modules provide already}.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Note:} If you're developing a WAR that requires a different
version of a third-party package that
\href{/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports}{Liferay
DXP or another module exports}, specify that package in your
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{\texttt{Import-Package:}
list}. If the package provider is an OSGi module, publish its exported
packages by deploying that module. Otherwise, rename the third-party
library (not an OSGi module) differently from the
\href{/docs/7-2/customization/-/knowledge_base/c/understanding-excluded-jars}{JAR
that the WAB generator excludes} and embed the JAR in your project.
\noindent\hrulefill
\textbf{Option 2 - Resolve the Java packages privately in your project}:
Copy \emph{required packages} only from libraries into your project, if
you can or embed \emph{libraries} wholesale, if you must. The rest of
this article shows you how to do these things.
\noindent\hrulefill
\textbf{Note:} Features for manipulating library packages are only
available to module projects that use bnd and the
\texttt{com.liferay.plugin} plugin, such as
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} modules. WAR projects must embed libraries wholesale into
their classpath.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Note}: Liferay's Gradle plugin \texttt{com.liferay.plugin}
automates several third party library configuration steps. The plugin is
automatically applied to
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} Gradle module projects created using
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio DXP} or
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Liferay Blade
CLI}.
To leverage the \texttt{com.liferay.plugin} plugin outside of Liferay
Workspace, add code like the listing below to your Gradle project and
update the version of the \texttt{com.liferay.gradle.plugins} artifact
to the latest version found in the repository:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins", version: "4.0.4"
}
repositories {
maven {
url "https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/"
}
}
}
apply plugin: "com.liferay.plugin"
\end{verbatim}
If you use Gradle without the \texttt{com.liferay.plugin} plugin, you
must \hyperref[embedding-libraries-using-gradle]{embed the third party
libraries wholesale}.
\noindent\hrulefill
The recommended package resolution workflow is next.
\section{Library Package Resolution
Workflow}\label{library-package-resolution-workflow}
When you depend on a library JAR, much of the time you only need parts
of it. Explicitly specifying only the Java packages you need makes your
module more modular. This also keeps other modules that depend on your
module from incorporating unneeded packages.
Here's a configuration workflow for module projects that minimizes
dependencies and Java package imports:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the library as a compile-only dependency (e.g.,
\texttt{compileOnly} in Gradle,
\texttt{\textless{}scope\textgreater{}provided\textless{}/scope\textgreater{}}
in Maven).
\item
Copy only the library packages you need by specifying them in a
conditional package instruction (\texttt{Conditional-Package}) in your
\texttt{bnd.bnd} file. Here are some examples:
\texttt{Conditional-Package:\ foo.common*} adds packages your module
uses such as \texttt{foo.common}, \texttt{foo.common-messages},
\texttt{foo.common-web} to your module's class path.
\texttt{Conditional-Package:\ foo.bar.*} adds packages your module
uses such as \texttt{foo.bar} and all its sub-packages (e.g.,
\texttt{foo.bar.baz}, \texttt{foo.bar.biz}, etc.) to your module's
class path.
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy
your project}. If a class your module needs or class its dependencies
need isn't found, go back to main workflow \textbf{Step 1 - Find an
OSGi module version of the library} to resolve it.
\textbf{Important}: Resolving packages by using compile-only
dependencies and conditional package instructions assures you use only
the packages you need and avoids unnecessary transitive dependencies.
It's recommended to use the steps up to this point, as much as
possible, to resolve required packages.
\item
If a library package you depend on requires non-class files (e.g.,
DLLs, descriptors) from the library, then you might need to
\hyperref[embedding-libraries-in-a-project]{embed the library
wholesale in your module}. This adds the entire library to your
module's classpath.
\end{enumerate}
Next you'll learn how to embed libraries in your module project.
\section{Embedding Libraries in a
Project}\label{embedding-libraries-in-a-project}
You can use Gradle or Maven to embed libraries in your project. Below
are examples for adding \href{https://shiro.apache.org}{Apache Shiro}
using both build utilities.
\section{Embedding Libraries Using
Gradle}\label{embedding-libraries-using-gradle}
Open your module's \texttt{build.gradle} file and add the library as a
dependency in the \texttt{compileInclude} configuration:
\begin{verbatim}
dependencies {
compileInclude group: 'org.apache.shiro', name: 'shiro-core', version: '1.1.0'
}
\end{verbatim}
The \texttt{com.liferay.plugin} plugin's \texttt{compileInclude}
configuration is transitive. The \texttt{compileInclude} configuration
embeds the artifact and all its dependencies in a \texttt{lib} folder in
the module's JAR. Also, it adds the artifact JARs to the module's
\texttt{Bundle-ClassPath} manifest header.
\textbf{Note}: The \texttt{compileInclude} configuration does not
download transitive
\href{https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html}{optional
dependencies}. If your module requires such artifacts, add them as you
would another third party library.
\textbf{Note:} If the library you've added as a dependency in your
\texttt{build.gradle} file has transitive dependencies, you can
reference them by name in an \texttt{-includeresource:} instruction
without having to add them explicitly to the dependency list. See how
it's used in the Maven section next.
\section{Embedding a Library Using
Maven}\label{embedding-a-library-using-maven}
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your project's \texttt{pom.xml} file and add the library as a
dependency in the \texttt{provided} scope:
\begin{verbatim}
org.apache.shiro
shiro-core
1.1.0
provided
\end{verbatim}
\item
Open your module's \texttt{bnd.bnd} file and add the library to an
\texttt{-includeresource} instruction:
\begin{verbatim}
-includeresource: META-INF/lib/shiro-core.jar=shiro-core-[0-9]*.jar;lib:=true
\end{verbatim}
This instruction adds the \texttt{shiro-core-{[}version{]}.jar} file
as an included resource in the module's \texttt{META-INF/lib} folder.
The \texttt{META-INF/lib/shiro-core.jar} is your module's embedded
library. The expression \texttt{{[}0-9{]}*} helps the build tool match
the library version to make available on the module's class path. The
\texttt{lib:=true} directive adds the embedded JAR to the module's
class path via the \texttt{Bundle-Classpath} manifest header.
\end{enumerate}
Lastly, if after embedding a library you get unresolved imports when
trying to deploy to Liferay, you might need to blacklist some imports:
\begin{verbatim}
Import-Package:\
!foo.bar.baz,\
*
\end{verbatim}
The \texttt{*} character represents all packages that the module refers
to explicitly. Bnd detects the referenced packages.
Congratulations! Resolving all of your module's package dependencies,
especially those from traditional Java libraries, is a quite an
accomplishment.
\section{Related Topics}\label{related-topics-2}
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{Importing
Packages}
\href{/docs/7-2/customization/-/knowledge_base/c/exporting-packages}{Exporting
Packages}
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Creating
a Project}
\chapter{Understanding Excluded JARs}\label{understanding-excluded-jars}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Module\%20Framework}{Portal
property \texttt{module.framework.web.generator.excluded.paths}}
declares JARs that are stripped from all Liferay DXP
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{generated
WABs}. These JARs are excluded from web application bundles (WABs)
because Liferay DXP provides them already. All JARs listed for this
property are excluded from a WAB, even if the WAB lists the JAR in a
\texttt{portal-dependency-jars} property in its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/liferay-plugin-package_7_2_0.properties.html}{\texttt{liferay-plugin-package.properties}}
file.
If your WAR requires different versions of the packages Liferay DXP
exports, you must include them in JARs named differently from the ones
\texttt{module.framework.web.generator.excluded.paths} excludes.
For example, Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/core/portal-bootstrap/system.packages.extra.bnd}{\texttt{system.packages.extra}}
module exports Spring Framework version 4.1.9 packages:
\begin{verbatim}
Export-Package:\
...
org.springframework.*;version='4.1.9',\
...
\end{verbatim}
Liferay DXP uses the
\texttt{module.framework.web.generator.excluded.paths} portal property
to exclude their JARs.
\begin{verbatim}
module.framework.web.generator.excluded.paths=\
...
WEB-INF/lib/spring-aop.jar,\
WEB-INF/lib/spring-aspects.jar,\
WEB-INF/lib/spring-beans.jar,\
WEB-INF/lib/spring-context.jar,\
WEB-INF/lib/spring-context-support.jar,\
WEB-INF/lib/spring-core.jar,\
WEB-INF/lib/spring-expression.jar,\
WEB-INF/lib/spring-jdbc.jar,\
WEB-INF/lib/spring-jms.jar,\
WEB-INF/lib/spring-orm.jar,\
WEB-INF/lib/spring-oxm.jar,\
WEB-INF/lib/spring-tx.jar,\
WEB-INF/lib/spring-web.jar,\
WEB-INF/lib/spring-webmvc.jar,\
WEB-INF/lib/spring-webmvc-portlet.jar,\
...
\end{verbatim}
To use a different Spring Framework version in your WAR, you must name
the corresponding Spring Framework JARs differently from the
glob-patterned JARs
\texttt{module.framework.web.generator.excluded.paths} lists.
For example, to use Spring Framework version 3.0.7's Spring AOP JAR,
include it in your plugin's \texttt{WEB-INF/lib} but name it something
other than \texttt{spring-aop.jar}. Adding the version to the JAR name
(i.e., \texttt{spring-aop-3.0.7.RELEASE.jar}) differentiates it from the
excluded JAR and prevents it from being stripped from the WAB (the
bundled WAR).
\section{Related Topics}\label{related-topics-3}
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Deploying
WARs (WAB Generator)}
\chapter{Using the Felix Gogo Shell}\label{using-the-felix-gogo-shell}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
The Gogo shell provides a way to interact with Liferay DXP's module
framework. You can
\begin{itemize}
\tightlist
\item
dynamically install/uninstall bundles
\item
examine package dependencies
\item
examine extension points
\item
list service references
\item
etc.
\end{itemize}
There are two ways you can access the Gogo shell.
The recommended way to access the Gogo shell for a production
environment is through the Control Panel. Accessing it there is the most
secure way to use the Gogo shell. You can set permissions in your
Liferay DXP instance to only give certain people access to it. The Gogo
shell is extremely powerful and should only be given to trusted admins,
as you can manipulate the platform's core functionality. You can access
the Gogo shell in the Control Panel by navigating to
\emph{Configuration} → \emph{Gogo Shell}.
You can also interact with Liferay DXP's module framework via a local
telnet session. This is only recommended when you're developing your
Liferay DXP instance. This is not recommended for production
environments.
To open the Gogo shell via telnet, execute the following command:
\begin{verbatim}
telnet localhost 11311
\end{verbatim}
Running this command requires a local running instance of Liferay DXP
and your machine's telnet command line utilities enabled. You must also
have
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes\#enabling-developer-mode-manually}{Developer
Mode enabled}.
To disconnect the session, execute the \texttt{disconnect} command.
Avoid using the following commands, which stop the OSGi framework:
\begin{itemize}
\tightlist
\item
\texttt{close}
\item
\texttt{exit}
\item
\texttt{shutdown}
\end{itemize}
If you have
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
installed and the telnet capability enabled, you can run the Gogo shell
via Blade command too:
\begin{verbatim}
blade sh
\end{verbatim}
Here are some useful Gogo shell commands:
\texttt{b\ {[}BUNDLE\_ID{]}}: lists information about a specific bundle
including the bundle's symbolic name, bundle ID, data root, registered
(provided) and used services, imported and exported packages, and more
\texttt{diag\ {[}BUNDLE\_ID{]}}: lists information about why the
specified bundle is not working (e.g., unresolved dependencies, etc.)
\texttt{headers\ {[}BUNDLE\_ID{]}}: lists metadata about the bundle from
the bundle's \texttt{MANIFEST.MF} file
\texttt{help}: lists all the available Gogo shell commands. Notice that
each command has two parts to its name, separated by a colon. For
example, the full name of the \texttt{help} command is
\texttt{felix:help}. The first part is the command scope while the
second part is the command function. The scope allows commands with the
same name to be disambiguated. E.g., scope allows the
\texttt{felix:refresh} command to be distinguished from the
\texttt{equinox:refresh} command.
\texttt{help\ {[}COMMAND\_NAME{]}}: lists information about a specific
command including a description of the command, the scope of the
command, and information about any flags or parameters that can be
supplied when invoking the command.
\texttt{inspect\ capability\ service\ {[}BUNDLE\_ID{]}}: lists services
exposed by a bundle
\texttt{install\ {[}PATH\_TO\_JAR\_FILE{]}}: installs the specified
bundle into Liferay's module framework
\texttt{lb}: lists all of the bundles installed in Liferay's module
framework. Use the \texttt{-s} flag to list the bundles using the
bundles' symbolic names.
\texttt{packages\ {[}PACKAGE\_NAME{]}}: lists all of the named package's
dependencies
\texttt{scr:list}: lists all of the components registered in the module
framework (\emph{scr} stands for service component runtime)
\texttt{scr:info\ {[}COMPONENT\_NAME{]}}: lists information about a
specific component including the component's description, services,
properties, configuration, references, and more.
\texttt{services}: lists all of the services that have been registered
in Liferay's module framework
\texttt{start\ {[}BUNDLE\_ID{]}}: starts the specified bundle
\texttt{stop\ {[}BUNDLE\_ID{]}}: stops the specified bundle
\texttt{uninstall\ {[}BUNDLE\_ID{]}}: uninstalls the specified bundle
from Liferay's module framework. This does not remove the specified
bundle from Liferay's module framework; it's hidden from Gogo's
\texttt{lb} command, but is still present. Adding a new version of the
uninstalled bundle, therefore, will not reinstall it; it will update the
currently hidden uninstalled version. To remove a bundle from Liferay's
module framework permanently, manually delete it from the
\texttt{LIFERAY\_HOME/osgi} folder. For more information on the
\texttt{uninstall} command, see OSGi's
\href{https://osgi.org/javadoc/r6/core/org/osgi/framework/Bundle.html\#uninstall()}{uninstall}
documentation.
For more information about the Gogo shell, visit
\href{http://felix.apache.org/documentation/subprojects/apache-felix-gogo.html}{Apache's
official documentation}.
\chapter{Importing Packages}\label{importing-packages}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Plugins often must use Java classes from packages outside of themselves.
Another OSGi bundle (a module or an OSGi Web Application Bundle) in the
OSGi framework must
\href{/docs/7-2/customization/-/knowledge_base/c/exporting-packages}{export}
a package for your plugin to import it.
When an OSGi bundle (bundle) is set up to import packages, the OSGi
framework finds other registered bundles that export the needed packages
and wires them to the importing bundle. At run time, the importing
bundle gets the class from the wired bundle that exports the class's
package.
For this to happen, a bundle's \texttt{META-INF/MANIFEST.MF} file must
specify the
\href{https://bnd.bndtools.org/heads/import_package.html}{\texttt{Import-Package}}
OSGi manifest header with a comma-separated list of the Java packages it
needs. For example, if a bundle needs classes from the
\texttt{javax.portlet} and \texttt{com.liferay.portal.kernel.util}
packages, it must specify them like so:
\begin{verbatim}
Import-Package: javax.portlet,com.liferay.portal.kernel.util,*
\end{verbatim}
The \texttt{*} character represents all packages that the module refers
to explicitly. Bnd detects the referenced packages.
Import packages must sometimes be specified manually, but not always.
Conveniently, Liferay DXP
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{project
templates} and
\href{/docs/7-2/reference/-/knowledge_base/r/tooling}{tools}
automatically detect the packages a bundle uses and add them to the
package imports in the bundle's manifest. Here are the different package
import scenarios:
\begin{itemize}
\item
\hyperref[automatic-package-import-generation]{Automatic Package
Import Generation}
\item
\hyperref[manually-adding-package-imports]{Manually Adding Package
Imports}
\end{itemize}
Let's explore how package imports are specified in these scenarios.
\section{Automatic Package Import
Generation}\label{automatic-package-import-generation}
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{Gradle
and Maven module projects} created using
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI},
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Liferay's Maven
archetypes}, or
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio DXP} use \href{http://bnd.bndtools.org/}{bnd}. On building
such a project's module JAR, bnd detects the packages the module uses
and generates a \texttt{META-INF/MANIFEST.MF} file whose
\texttt{Import-Package} header specifies the packages.
\noindent\hrulefill
\textbf{Note:} Liferay's Maven module archetypes use the
\texttt{bnd-maven-plugin}. Liferay's Gradle module project templates use
\href{https://github.com/TomDmitriev/gradle-bundle-plugin}{a third-party
Gradle plugin} to invoke bnd.
\noindent\hrulefill
For example, suppose you're developing a Liferay module using Maven or
Gradle. In most cases, you specify your module's dependencies in your
\texttt{pom.xml} or \texttt{build.gradle} file. At build time, the Maven
or Gradle module plugin reads your \texttt{pom.xml} or
\texttt{build.gradle} file and bnd adds the required
\texttt{Import-Package} headers to your module JAR's
\texttt{META-INF/MANIFEST.MF}.
Here's an example dependencies section from a module's
\texttt{build.gradle} file:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
}
\end{verbatim}
And here's the \texttt{Import-Package} header that's generated in the
module JAR's \texttt{META-INF/MANIFEST.MF} file:
\begin{verbatim}
Import-Package: com.liferay.portal.kernel.portlet.bridges.mvc;version=
"[1.0,2)",com.liferay.portal.kernel.util;version="[7.0,8)",javax.nami
ng,javax.portlet;version="[2.0,3)",javax.servlet,javax.servlet.http,j
avax.sql
\end{verbatim}
Note that your build file need only specify artifact dependencies. bnd
examines your module's class path to determine which packages from those
artifacts contain classes your application uses and imports the
packages. The examination includes all classes found in the class
path--even those from embedded
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{third
party library JARs}.
Regarding classes used by a plugin WAR,
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Liferay's
WAB Generator} detects their use in the WAR's JSPs, descriptor files,
and classes (in \texttt{WEB-INF/classes} and embedded JARs). The WAB
Generator searches the \texttt{web.xml}, \texttt{liferay-web.xml},
\texttt{portlet.xml}, \texttt{liferay-portlet.xml}, and
\texttt{liferay-hook.xml} descriptor files. It adds package imports for
classes that are neither found in the plugin's \texttt{WEB-INF/classes}
folder nor in its embedded JARs.
\noindent\hrulefill
\textbf{Note:} Packages for Java APIs, such as Java Portlet, aren't
semantically versioned but have Portable Java Contracts. Each API's
contract specifies the JSR it satisfies. Bundles that use these APIs
must specify requirements on the API contracts. The contract requirement
specifies your bundle's relationship with the imported API packages. If
the system you're running does \emph{not} provide the exact contract,
your bundle does not resolve. Resolving the missing package is better
than handling an incompatibility failure during execution.
\begin{itemize}
\item
\textbf{Blade CLI and Liferay Dev Studio DXP module projects} specify
Portable Java Contracts automatically! For example, if your Blade CLI
or Liferay Dev Studio DXP module uses the Java Portlet API and you
compile against the Java Portlet 2.0 artifact, a contract requirement
for the package is added to your module's manifest.
\item
\textbf{Module projects that use bnd but are not created using Blade
CLI or Liferay Dev Studio DXP} must specify contracts in their
\texttt{bnd.bnd} file. For example, here are contract instructions for
Java Portlet and Java Servlet APIs:
\begin{verbatim}
-contract: JavaPortlet,JavaServlet
\end{verbatim}
At build time, bnd adds the contract instructions to your module's
manifest. It adds a requirement for the first version of the API found
in your classpath and \emph{removes} version range information from
\texttt{Import-Package} entries for corresponding API packages---the
package version information isn't needed.
\item
\textbf{Projects that don't use bnd} must specify contracts in their
OSGi bundle manifest. For example, here's the specified contract for
\texttt{JavaPortlet} 2.0, which goes in your
\texttt{META-INF/MANIFEST.MF} file:
\begin{verbatim}
Import-Package: javax.portlet
Require-Capability: osgi.contract;filter:=(&(osgi.contract=JavaPortlet)(version=2.0))
\end{verbatim}
\end{itemize}
For Portable Java Contract details, see
\href{https://www.osgi.org/portable-java-contract-definitions/}{Portable
Java Contract Definitions}.
\noindent\hrulefill
\section{Manually Adding Package
Imports}\label{manually-adding-package-imports}
The WAB Generator and bnd don't add package imports for classes
referenced in these places:
\begin{itemize}
\tightlist
\item
Unrecognized descriptor file
\item
Custom or unrecognized descriptor element or attribute
\item
Reflection code
\item
Class loader code
\end{itemize}
In such cases, you must manually determine these packages and specify an
\texttt{Import-Package} OSGi header that includes these packages and the
packages that Bnd detects automatically. The \texttt{Import-Package}
header belongs in the location appropriate to your project type:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.2727}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.7273}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Project type
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
\texttt{Import-Package} header location
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Module (uses bnd) & \texttt{{[}project{]}/bnd.bnd} \\
Module (doesn't use bnd) &
\texttt{{[}module\ JAR{]}/META-INF/MANIFEST.MF} \\
Traditional Liferay plugin WAR &
\texttt{WEB-INF/liferay-plugin-package.properties} \\
\end{longtable}
\noindent\hrulefill
Here's an example of adding a package called
\texttt{com.liferay.docs.foo} to the list of referenced packages that
Bnd detects automatically:
\begin{verbatim}
Import-Package:\
com.liferay.docs.foo,\
*
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} The
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{WAB
Generator} refrains from adding WAR project embedded third-party JARs to
a WAB if
\href{/docs/7-2/customization/-/knowledge_base/c/understanding-excluded-jars}{Liferay
DXP already exports the JAR's packages}.
If your WAR requires a different version of a third-party package that
Liferay DXP exports, specify that package in your
\texttt{Import-Package:} list. Then if the package provider is an OSGi
module, publish its exported packages by deploying the module. If the
package provider is not an OSGi module, follow the instructions for
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{adding
third-party libraries}.
\noindent\hrulefill
Please see the
\href{https://bnd.bndtools.org/heads/import_package.html}{\texttt{Import-Package}}
header documentation for more information.
Congratulations! Now you can import all kinds of packages for your
modules and plugins to use.
\section{Related Topics}\label{related-topics-4}
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Deploying
WARs (WAB Generator)}
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{Project
Templates}
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Liferay's Maven
Archetypes}
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio DXP}
\chapter{Exporting Packages}\label{exporting-packages}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
An OSGi bundle's Java packages are private by default. To expose a
package, you must explicitly export it. This way you share only the
classes you want to share. Exporting a package in your OSGi bundle
(bundle) manifest makes all the package's classes available for other
bundles to
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{import}.
To export a package, add it to your module's or plugin's
\texttt{Export-Package} OSGi header. A header exporting
\texttt{com.liferay.petra.io} and \texttt{com.liferay.petra.io.unsync}
would look like this:
\begin{verbatim}
Export-Package:\
com.liferay.petra.io,\
com.liferay.petra.io.unsync
\end{verbatim}
The correct location for the header depends on your project's type:
\noindent\hrulefill
\begin{longtable}[]{@{}ll@{}}
\toprule\noalign{}
Project Type & \texttt{Export-Package} header location \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Module JAR (uses bnd) & \texttt{{[}project{]}/bnd.bnd} \\
Module JAR (doesn't use bnd) &
\texttt{{[}module\ JAR{]}/META-INF/MANIFEST.MF} \\
Plugin WAR & \texttt{WEB-INF/liferay-plugin-package.properties} \\
\end{longtable}
\noindent\hrulefill
Module projects created using
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI},
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Liferay's Maven
archetypes}, or
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio DXP} use \href{http://bnd.bndtools.org/}{bnd}. On building
such a project's module JAR, bnd propagates the OSGi headers from the
project's \texttt{bnd.bnd} file to the JAR's
\texttt{META-INF/MANIFEST.MF}.
In module projects that don't use bnd, you must manually add package
exports to an \texttt{Export-Package} header in the module JAR's
\texttt{META-INF/MANIFEST.MF}.
In plugin WAR projects, you must add package exports to an
\texttt{Export-Package} header in the project's
\texttt{WEB-INF/liferay-plugin-package.properties}. On copying the WAR
into the \texttt{{[}Liferay\ Home{]}/deploy} folder, the
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{WAB
Generator} propagates the OSGi headers from the WAR's
\texttt{liferay-plugin-package.properties} file to the
\texttt{META-INF/MANIFEST.MF} file in the generated Web Application
Bundle (WAB).
\noindent\hrulefill
\textbf{Note:} bnd makes a module's exported packages
\emph{substitutable}. That is, the OSGi framework can substitute your
module's exported package with a compatible package of the same name,
but potentially different version, that's exported from a different OSGi
bundle. bnd enables this for your module by automatically making your
module import every package it exports. In this way, your module can
work on its own, but can also work in conjunction with bundles that
provide a different (compatible) version, or even the same version, of
the package. A package from another bundle might provide better
``wiring'' opportunities with other bundles.
\href{http://blog.osgi.org/2007/04/importance-of-exporting-nd-importing.html}{Peter
Kriens' blog post} provides more details on how substitutable exports
works.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Important:} Don't export the same package from different JARs.
Multiple exports of the same package leads to ``split package'' issues,
whose side affects differ from case to case.
\noindent\hrulefill
Now you can share your module's or plugin's terrific {[}EDITOR: or
terrible!{]} packages with other OSGi bundles!
\section{Related Topics}\label{related-topics-5}
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Deploying
WARs (WAB Generator)}
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{Project
Templates}
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Liferay's Maven
Archetypes}
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio DXP}
\href{/docs/7-2/customization/-/knowledge_base/c/semantic-versioning}{Semantic
Versioning}
\chapter{Semantic Versioning}\label{semantic-versioning}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
\href{https://semver.org}{Semantic Versioning} is a three tiered
versioning system that increments version numbers based on the type of
API change introduced to a releasable software component. It's a
standard way of communicating programmatic compatibility of a package or
module for dependent consumers and API implementations. If a package is
programmatically (i.e., semantically) incompatible with a project,
\href{http://bnd.bndtools.org}{bnd} (used when building
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Liferay
generated module projects}) fails that project's build immediately.
The semantic version format looks like this:
\begin{verbatim}
MAJOR.MINOR.MICRO
\end{verbatim}
Certain events force each tier to increment:
\begin{itemize}
\tightlist
\item
\emph{MAJOR:} an incompatible, API-breaking change is made
\item
\emph{MINOR:} a change that affects only providers of the API, or new
backwards- compatible functionality is added
\item
\emph{MICRO:} a backwards-compatible bug fix is made
\end{itemize}
For more details on semantic versioning, see the official
\href{https://semver.org/}{Semantic Versioning} site and
\href{http://www.osgi.org/wp-content/uploads/SemanticVersioning1.pdf}{OSGi
Alliance's Semantic Versioning} technical whitepaper.
All of Liferay DXP's modules use Semantic Versioning.
Following Semantic Versioning is especially important because Liferay
DXP is a modular platform containing hundreds of independent OSGi
modules. With many independent modules containing a slew of
dependencies, releasing new package versions can quickly become
terrifying. With this complex intertwined system of dependencies, you
must meticulously manage your own project's API versions to ensure
compatibility for those who leverage it. With Semantic Versioning's
straightforward system and the help of
\href{/docs/7-2/reference/-/knowledge_base/r/tooling}{Liferay tooling},
managing your module project's versions is easy.
\section{Baselining Your Project}\label{baselining-your-project}
Following Semantic Versioning manually seems deceptively easy. There's a
sad history of good-intentioned developers updating their projects'
semantic versions manually, only to find out later they made a mistake.
The truth is, it's hard to anticipate the ramifications of a simple
update. To avoid this, you can \emph{baseline} your project after it has
been updated. Baselining verifies that the Semantic Versioning rules are
obeyed by your project. This can catch many obvious API changes that are
not so obvious to humans. Care must always be taken, however, when
making any kind of code change because this tool is not smart enough to
identify compatibility changes not represented in the signatures of Java
classes or interfaces, or in API \emph{use} changes (e.g., assumptions
about method call order, or changes to input and/or output encoding).
Baseline, as the name implies, does give you a certain measure of
\emph{baseline} comfort that a large class of compatibility issues won't
sneak past you.
You can use Liferay's Baseline Gradle plugin to provide baselining
capabilities. Add it to your Gradle build configuration and execute the
following command:
\begin{verbatim}
./gradlew baseline
\end{verbatim}
See the
\href{/docs/7-2/reference/-/knowledge_base/r/baseline-gradle-plugin}{Baseline
Gradle Plugin} article for configuration details. This plugin is not
provided in
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} by default.
When you run the \texttt{baseline} command, the plugin baselines your
new module against the latest released non-snapshot module (i.e., the
baseline). That is, it compares the public exported API of your new
module with the baseline. If there are any changes, it uses the OSGi
Semantic Versioning rules to calculate the minimum new version. If your
new module has a lower version, errors are thrown.
With baselining, your project's Semantic Versioning is as accurate as
its API expresses.
\section{Managing Artifact and Dependency
Versions}\label{managing-artifact-and-dependency-versions}
There are two ways to track your project's artifact and dependency
versions with Semantic Versioning:
\begin{itemize}
\tightlist
\item
Range of versions
\item
Exact version (one-to-one)
\end{itemize}
You should track a range of versions if you intend to build your project
for multiple versions of Liferay DXP and maintain maximum compatibility.
In other words, if several versions of a package work for an app, you
can configure the app to use any of them. What's more, bnd automatically
determines the semantically compatible range of each package a module
depends on and records the range to the module's manifest.
For help with version range syntax, see the
\href{https://osgi.org/specification/osgi.core/7.0.0/framework.module.html\#i3189032}{OSGi
Specifications}.
A version range for imported packages in an OSGi bundle's
\texttt{bnd.bnd} looks like this:
\begin{verbatim}
Import-Package: com.liferay.docs.test; version="[1.0.0,2.0.0)"
\end{verbatim}
Popular build tools also follow this syntax. In Gradle, a version range
for a dependency looks like this:
\begin{verbatim}
compile group: "com.liferay.portal", name: "com.liferay.portal.test", version: "[1.0.0,2.0.0)"
\end{verbatim}
In Maven, it looks like this:
\begin{verbatim}
com.liferay.portal
com.liferay.portal.test
[1.0.0,2.0.0)
\end{verbatim}
Specifying the latest release version can also be considered a range of
versions with no upper limit. For example, in Gradle, it's specified as
\texttt{version:\ "latest.release"}. This can be done in Maven 2.x with
the usage of the version marker \texttt{RELEASE}. This is not possible
if you're using Maven 3.x. See \href{https://gradle.org/docs}{Gradle}
and \href{http://maven.apache.org/guides/}{Maven}'s respective docs for
more information.
Tracking a range of versions comes with a price. It's hard to reproduce
old builds when you're debugging an issue. It also comes with the risk
of differing behaviors depending on the version used. Also, relying on
the latest release could break compatibility with your project if a
major change is introduced. You should proceed with caution when
specifying a range of versions and ensure your project is tested on all
included versions.
Tracking a dependency's exact version is much safer, but is less
flexible. This might limit you to a specific version of Liferay DXP. You
would also be locked in to APIs that only exist for that specific
version. This means your module is much easier to test and has less
chance for unexpected failures.
\noindent\hrulefill
\textbf{Note:} When specifying package versions in your \texttt{bnd.bnd}
file, exact versions are typically specified like this:
\texttt{version="1.1.2"}. However, this syntax is technically a range;
it is interpreted as {[}1.1.2, ∞). Therefore, if a higher version of the
package is available, it's used instead of the version you specified.
For these cases, it may be better to specify a version range for
compatible versions that have been tested. If you want to specify a true
exact match, the syntax is like this: \texttt{{[}1.1.2{]}}. See the
\href{https://osgi.org/specification/osgi.core/7.0.0/framework.module.html\#i3189032}{Version
Range} section in the OSGi specifications for more info.
Gradle and Maven use exact versions when only one version is specified.
\noindent\hrulefill
You now know the pros and cons for tracking dependencies as a range and
as an exact match.
\section{Related Topics}\label{related-topics-6}
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{Importing
Packages}
\href{/docs/7-2/customization/-/knowledge_base/c/exporting-packages}{Exporting
Packages}
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}
\chapter{Deploying WARs (WAB
Generator)}\label{deploying-wars-wab-generator}
You can create applications for Liferay DXP as Java EE-style Web
Application ARchive (WAR) artifacts or as Java ARchive (JAR) OSGi bundle
artifacts. Bean Portlets, PortletMVC4Spring Portlets, and JSF Portlets
must be packaged as WAR artifacts because their frameworks are designed
for Java EE. Therefore, they expect a WAR layout and require Java EE
resources such as the \texttt{WEB-INF/web.xml} descriptor.
Liferay provides a way for these WAR-styled plugins to be deployed and
treated like OSGi modules by Liferay's OSGi runtime. They can be
converted to \emph{WABs}.
Liferay DXP supports the OSGi Web Application Bundle (WAB) standard for
deployment of Java EE style WARs. Simply put, a WAB is an archive that
has a WAR layout and contains a \texttt{META-INF/MANIFEST.MF} file with
the \texttt{Bundle-SymbolicName} OSGi directive. A WAB is an OSGi
bundle. Although the project source has a WAR layout, the artifact
filename may end with either the \texttt{.jar} or \texttt{.war}
extension.
Liferay only supports the use of WABs that have been auto-generated by
the WAB Generator. The WAB Generator transforms a traditional WAR-style
plugin into a WAB during deployment. So what exactly does the WAB
Generator do to a WAR file to transform it into a WAB?
The WAB Generator detects packages referenced in the plugin WAR's JSPs,
descriptor files, and classes (in \texttt{WEB-INF/classes} and embedded
JARs). The descriptor files include \texttt{web.xml},
\texttt{liferay-web.xml}, \texttt{portlet.xml},
\texttt{liferay-portlet.xml}, and \texttt{liferay-hook.xml}. The WAB
Generator verifies whether the detected packages are in the plugin's
\texttt{WEB-INF/classes} folder or in an embedded JAR in the
\texttt{WEB-INF/lib} folder. Packages that aren't found in either
location are added to an \texttt{Import-Package} OSGi header in the
WAB's \texttt{META-INF/MANIFEST.MF} file.
To import a package that is only referenced in the following types of
locations, you must add an \texttt{Import-Package} OSGi header to the
plugin's \texttt{WEB-INF/liferay-plugin-package.properties} file and add
the package to that header's list of values.
\begin{itemize}
\tightlist
\item
Unrecognized descriptor file
\item
Custom or unrecognized descriptor element or attribute
\item
Reflection code
\item
Class loader code
\end{itemize}
\section{WAR versus WAB Structure}\label{war-versus-wab-structure}
The WAB folder structure and WAR folder structure differ. Consider the
following folder structure of a WAR-style portlet.
\textbf{WAR}
\begin{itemize}
\tightlist
\item
\texttt{my-war-portlet}
\begin{itemize}
\tightlist
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\item
\texttt{webapp}
\begin{itemize}
\tightlist
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{classes}
\item
\texttt{lib}
\item
\texttt{resources}
\item
\texttt{views}
\item
\texttt{liferay-display.xml}
\item
\texttt{liferay-plugin-package.properties}
\item
\texttt{liferay-portlet.xml}
\item
\texttt{portlet.xml}
\item
\texttt{web.xml}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
When a WAR-style portlet is deployed to Liferay DXP and processed by the
WAB Generator, the portlet's folder structure is transformed.
\textbf{WAB}
\begin{itemize}
\tightlist
\item
\texttt{my-war-portlet-that-is-now-a-wab}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{MANIFEST.MF}
\end{itemize}
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{classes}
\item
\texttt{lib}
\item
\texttt{resources}
\item
\texttt{views}
\item
\texttt{liferay-display.xml}
\item
\texttt{liferay-plugin-package.properties}
\item
\texttt{liferay-portlet.xml}
\item
\texttt{portlet.xml}
\item
\texttt{web.xml}
\end{itemize}
\end{itemize}
\end{itemize}
The major difference is the addition of the
\texttt{META-INF/MANIFEST.MF} file. The WAB Generator automatically
generates an OSGi-ready manifest file. If you want to affect the content
of the manifest file, you can place bnd directives and OSGi headers
directly into your plugin's \texttt{liferay-plugin-package.properties}
file.
\noindent\hrulefill
\textbf{Note:} Adding a \texttt{bnd.bnd} file or a build-time plugin
(e.g., \texttt{bnd-maven-plugin}) to your WAR plugin is pointless,
because the generated WAB cannot use them.
\noindent\hrulefill
\section{Deploying a WAR}\label{deploying-a-war}
To deploy a WAB based on your WAR plugin, copy your WAR plugin to your
Liferay DXP instance's \texttt{deploy/} folder in your
\href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\texttt{{[}Liferay\ Home{]}}}.
\section{Saving a Copy of the WAB}\label{saving-a-copy-of-the-wab}
Optionally, save the WAB to a local folder. This gives you the
opportunity to inspect the generated WAB. To store generated WABs, add
the following portal properties to a
\texttt{{[}Liferay\ Home{]}/portal-ext.properties} file. Then restart
Liferay DXP:
\begin{verbatim}
module.framework.web.generator.generated.wabs.store=true
module.framework.web.generator.generated.wabs.store.dir=${module.framework.base.dir}/wabs
\end{verbatim}
These properties instruct the WAB generator to store generated WABs in
your Liferay instance's \texttt{osgi/wabs/} folder. The generated WABs
have the same structure as the example WAB structure listed above. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Module\%20Framework\%20Web\%20Application\%20Bundles}{Module
Framework Web Application Bundles} properties section explains more
details.
Awesome! You have deployed your WAR plugin as a WAB and you know how to
save a copy of the WAB to examine it!
\section{Related Topics}\label{related-topics-7}
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Creating
a Project}
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploying
a Project}
\href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{Developing Web
Front-Ends}
\chapter{Architecture}\label{architecture}
Liferay DXP architecture comprises these parts:
\textbf{Core:} Bootstraps Liferay DXP and its frameworks. The Core
provides a runtime environment for managing services, UI components, and
customizations.
\textbf{Services:} Liferay and custom functionality is exposed via Java
APIs and web APIs.
\textbf{UI:} The optional web application UI for adding portals, sites,
pages, widgets, and content.
You can use the Liferay DXP UI and services together or focus solely on
using services via
\href{/docs/7-2/frameworks/-/knowledge_base/f/headless-rest-apis}{REST
web APIs}.
\begin{figure}
\centering
\includegraphics{./images/architecture-options.png}
\caption{Liferay DXP portals and Sites contain content and widgets.
Liferay DXP can also be used ``headless''---without the UI.}
\end{figure}
The architecture satisfies these requirements:
\begin{itemize}
\item
Supports using common development technologies
\item
Leverages development standards
\item
Facilitates swapping components
\item
Starts fast and performs well
\item
Its runtime is easy to configure and inspect
\end{itemize}
The Core supports UI and service deployments and orchestrates wiring
them together.
\section{Core}\label{core}
Liferay DXP is a web application that runs on your application server.
The Core bootstraps the application and
\href{/docs/7-2/frameworks/-/knowledge_base/f/frameworks}{Liferay's
built-in frameworks}.
There are frameworks for these things and more:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media}{Adaptive
Media}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configurable-applications}{Application
Configuration}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/application-security}{Application
Security}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset
Framework}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{File
Management}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/search}{Search}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/segmentation-personalization}{Segmentation
and Personalization}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes}{Upgrade
Processes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{Web
Fragments}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{Workflow}
\end{itemize}
The Core provides the component runtime environment for the frameworks,
services, and UI. Here are some component examples:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Services}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers}{Service
customizations}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
(templates, controllers, and resources)
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{JavaScript
applications} (templates, routers, and resources)
\item
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{JSP
customization via Portlet Filters}
\item
\href{(/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction)}{Theme}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-language-module}{Shared
Language Keys}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/screen-navigation-framework}{Navigation
components}
\end{itemize}
The following figure shows these component types in the runtime
environment.
\begin{figure}
\centering
\includegraphics{./images/component-runtime-environment.png}
\caption{The Core provides a runtime environment for components, such as
the ones here. New component implementations can extend or replace
existing implementations dynamically.}
\end{figure}
The runtime environment supports adding, replacing, and customizing
components on-the-fly. This makes the following scenarios possible:
\textbf{Replacement:} If the \texttt{ServiceC\ Impl\ 2} component has a
higher ranking than existing component \texttt{ServiceC\ Impl\ 1},
\texttt{ServiceC\ Impl\ 2} is used in its place.
\textbf{Customization:} The \texttt{PortletA\ Filter} intercepts and
modifies requests to and responses from \texttt{PortletA}, affecting the
content \texttt{PortletA} displays.
Component WAR and module JAR projects install as
\href{https://www.osgi.org/}{OSGi bundles} (modules). Liferay DXP's OSGi
framework defines the module lifecycle, enforces dependencies, defines
the class loading structure, and provides an API and CLI
(\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix
Gogo Shell}) for managing modules and components. The Core is configured
via \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal
properties files} and
\href{/docs/7-2/user/-/knowledge_base/u/server-administration}{Server
Administration panels}.
The service components provide business functionality.
\section{Services}\label{services}
Business logic is implemented in services deployed to the component
runtime environment. Built-in Core services and framework services
operate on Liferay models such as Users, Roles, Web Content, Documents
and Media, and more. You can write and deploy custom services to
introduce new models and functionality. Service components can access
each other in Liferay DXP via
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{dependency
injection}.
Front-end applications invoke the services to do work. You can deploy
Java-based applications that call services directly using the
\href{/docs/7-2/reference/-/knowledge_base/r/java-apis}{Java APIs}, and
any web-based (Java and non-Java) application, whether deployed on
Liferay DXP or not, can use the web APIs, which include
\href{/docs/7-2/appdev/-/knowledge_base/a/generating-apis-with-rest-builder}{headless
REST APIs} that conform to the
\href{https://swagger.io/docs/specification/about/}{OpenAPI} standard
and include
\href{/docs/7-2/frameworks/-/knowledge_base/f/web-services}{plain
web/REST services}. The following figure shows Liferay DXP applications
and external clients invoking Liferay services.
\begin{figure}
\centering
\includegraphics{./images/apps-invoking-services.png}
\caption{Remote and Liferay DXP applications can invoke services via
REST web APIs. Liferay DXP Java-based portlets can also invoke services
via Java APIs.}
\end{figure}
Liferay services are built using
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} and made REST-ful using
\href{/docs/7-2/appdev/-/knowledge_base/a/rest-builder}{REST Builder}.
The services are easy to
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-osgi-services}{override
and extend} too.
Liferay DXP also provides a web-based UI, which makes content and
service functionality available in browsers.
\section{UI}\label{ui}
\href{/docs/7-2/user/-/knowledge_base/u/the-liferay-distinction}{Liferay
DXP's UI} helps people do work,
\href{/docs/7-2/user/-/knowledge_base/u/collaboration}{collaborate}, and
\href{/docs/7-2/user/-/knowledge_base/u/web-experience-management}{enjoy
content}. The UI consists of
\begin{itemize}
\item
\href{/docs/7-2/user/-/knowledge_base/u/the-liferay-distinction}{Liferay
DXP application}: The web application for managing Portals, Sites,
Users, Pages, Widgets, and more.
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/application-development}{Applications}:
Widgets that provide a user interface for services already deployed.
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes}:
Plugins for styling Sites with a unique look and feel.
\end{itemize}
The UI concepts article digs deeper into developing and customizing UI
components.
As you can see, the Liferay DXP architecture supports developing
services, UI components, and customizations. The architecture section
covers Core, service, and UI topics. Next, we dive into the Core to
describe class loading, modularity, and more. But you can jump ahead to
any service or UI architecture topics, if you like. Enjoy exploring the
Liferay DXP architecture!
\chapter{Liferay Portal Classloader
Hierarchy}\label{liferay-portal-classloader-hierarchy}
All Liferay DXP applications live in its OSGi container. Portal is a web
application deployed on your application server. Portal's Module
Framework bundles (modules) live in the OSGi container and have
classloaders. All the classloaders from Java's Bootstrap classloader to
classloaders for bundle classes and JSPs are part of a hierarchy.
This article explains Liferay's classloader hierarchy and describes how
it works in the following contexts:
\begin{itemize}
\tightlist
\item
Web application, such as Liferay Portal, deployed on the app server
\item
OSGi bundle deployed in the Module Framework
\end{itemize}
The following diagram shows Liferay DXP's classloader hierarchy.
\begin{figure}
\centering
\includegraphics{./images/portal-classloader-hierarchy.png}
\caption{0: Here is Liferay's classloader hierarchy.}
\end{figure}
Here are the classloader descriptions:
\begin{itemize}
\item
\textbf{Bootstrap}: The JRE's classes (from packages \texttt{java.*})
and Java extension classes (from \texttt{\$JAVA\_HOME/lib/ext}). No
matter the context, loading all \texttt{java.*} classes is delegated
to the Bootstrap classloader.
\item
\textbf{System}: Classes configured on the \texttt{CLASSPATH} and or
passed in via the application server's Java classpath (\texttt{-cp} or
\texttt{-classpath}) parameter.
\item
\textbf{Common}: Classes accessible globally to web applications on
the application server.
\item
\textbf{Web Application}: Classes in the application's
\texttt{WEB-INF/classes} folder and \texttt{WEB-INF/lib/*.jar}.
\item
\textbf{Module Framework}: Liferay's OSGi module framework classloader
which is used to provide controlled isolation for the module framework
bundles.
\item
\textbf{bundle}: Classes from a bundle's packages or from packages
other bundles export.
\item
\textbf{JSP}: A classloader that aggregates the following bundle and
classloaders:
\begin{itemize}
\tightlist
\item
Bundle that contains the JSPs' classloader
\item
JSP servlet bundle's classloader
\item
Javax Expression Language (EL) implementation bundle's classloader
\item
Javax JSTL implementation bundle's classloader
\end{itemize}
\item
\textbf{Service Builder}: Service Builder classes
\end{itemize}
The classloader used depends on context. Classloading rules vary between
application servers. Classloading in web applications and OSGi bundles
differs too. In all contexts, however, the Bootstrap classloader loads
classes from \texttt{java.*} packages.
Classloading from a web application perspective is up next.
\section{Web Application Classloading
Perspective}\label{web-application-classloading-perspective}
Application servers dictate where and in what order web applications,
such as Liferay DXP, search for classes and resources. Application
servers such as
\href{https://tomcat.apache.org/tomcat-9.0-doc/class-loader-howto.html}{Apache
Tomcat} enforce the following default search order:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Bootstrap classes
\item
Web app's \texttt{WEB-INF/classes}
\item
web app's \texttt{WEB-INF/lib/*.jar}
\item
System classloader
\item
Common classloader
\end{enumerate}
First, the web application searches Bootstrap. If the class/resource
isn't there, the web application searches its own classes and JARs. If
the class/resource still isn't found, it checks the System classloader
and then Common classloader. Except for the web application checking its
own classes and JARs, it searches the hierarchy in parent-first order.
Application servers such as
\href{https://docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html}{Oracle
WebLogic} and IBM WebSphere have additional classloaders. They may also
have a different classloader hierarchy and search order. Consult your
application server's documentation for classloading details.
\section{Other Classloading
Perspectives}\label{other-classloading-perspectives}
\href{/docs/7-2/customization/-/knowledge_base/c/bundle-classloading-flow}{Bundle
Classloading Flow} explains classloading from an OSGi bundle
perspective.
Classloading for JSPs and Service Builder classes is similar to that of
web applications and OSGi bundle classes.
You now know Liferay DXP's classloading hierarchy, understand it in
context of web applications, and have references to information on other
classloading perspectives.
\section{Related Topics}\label{related-topics-8}
\href{/docs/7-2/customization/-/knowledge_base/c/bundle-classloading-flow}{Bundle
Classloading Flow}
\chapter{Liferay DXP Startup Phases}\label{liferay-dxp-startup-phases}
Knowing Liferay's startup phases helps you troubleshoot startup
failures. By learning the phase triggered events, you can listen for
phases and act on them. This article describes the startup phases and
identifies how to \hyperref[acting-on-events]{implement actions for
phase events}.
Startup consists of these main phases:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\textbf{Portal Context Initialization Phase:} focuses on low level
tasks without a web context.
\item
\textbf{Main Servlet Initialization Phase:} focuses on the portlet
container and the Liferay DXP web application's UI features such as
Struts, Themes, and more.
\end{enumerate}
The Portal Context Initialization Phase sets the stage for the Main
Servlet Initialization Phase.
\section{Portal Context Initialization
Phase}\label{portal-context-initialization-phase}
The Portal Context Initialization phase runs first with these tasks:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Set up low level utilities such as logging and those in
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/PortalUtil.html}{\texttt{PortalUtil}}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/util/InitUtil.html}{\texttt{InitUtil}}.
\item
OSGi framework is initialized.
\item
Spring Phase 1: INFRASTRUCTURE beans specified by the Spring context
files listed in Portal property
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Spring}{\texttt{spring.infrastructure.configs}}
are loaded.
\item
INFRASTRUCTURE beans are published as
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi
services}.
\item
OSGi framework starts.
\begin{enumerate}
\def\labelenumii{\arabic{enumii}.}
\tightlist
\item
Static bundles are installed and started.
\item
Dynamic bundles are started.
\end{enumerate}
\item
OSGi framework starts the runtime.
\item
Spring Phase 2: MAIN
\begin{enumerate}
\def\labelenumii{\arabic{enumii}.}
\tightlist
\item
Load Spring beans specified by the Spring context files listed in
Portal property
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Spring}{\texttt{spring.configs}}.
\item
A
\hyperref[moduleservicelifecycle-events]{\texttt{ModuleServiceLifecycle}
event service} with a service property
\texttt{module.service.lifecycle} value \texttt{spring.initialized}
(i.e.,
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/constant-values.html\#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.SPRING_INITIALIZED}{\texttt{SPRING\_INITIALIZED}})
registers.
\end{enumerate}
\item
MAIN Spring beans are published as
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi
services}.
\end{enumerate}
\section{Main Servlet Initialization
Phase}\label{main-servlet-initialization-phase}
Here's the phase's activity sequence:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The
\hyperref[moduleservicelifecycle-events]{\texttt{ModuleServiceLifecycle}
event service} is updated with the service property
\texttt{module.service.lifecycle} value \texttt{database.initialized}
(i.e.,
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/constant-values.html\#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.DATABASE_INITIALIZED}{\texttt{DATABASE\_INITIALIZED}}).
\item
The \hyperref[portal-startup-events]{Global Startup event} fires.
\item
For each portal instance, the
\hyperref[portal-startup-events]{Application Startup events} fire.
\item
The
\hyperref[moduleservicelifecycle-events]{\texttt{ModuleServiceLifecycle}
event service} is updated with the service property
\texttt{module.service.lifecycle} value \texttt{portal.initialized}
(i.e.,
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/constant-values.html\#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.PORTAL_INITIALIZED}{\texttt{PORTAL\_INITIALIZED}}).
\end{enumerate}
Now that you're acquainted with the startup phases, you can concentrate
on the events they fire.
\section{Acting on Events}\label{acting-on-events}
The ways to act on events depends on the event type. These subsections
describe the event types.
\section{ModuleServiceLifecycle
Events}\label{moduleservicelifecycle-events}
\href{/docs/7-2/customization/-/knowledge_base/c/waiting-on-lifecycle-events}{You
can wait for and act on \texttt{ModuleServiceLifecycle} event services.}
\section{Portal Startup Events}\label{portal-startup-events}
In your \texttt{liferay-portal-ext.properties} file, you can override
the following properties and add your own
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/events/LifecycleAction.html}{\texttt{LifecycleAction}}
classes to the list of action classes to invoke on the events.
\textbf{Global Startup Event} runs once when Liferay DXP initializes.
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Startup\%20Events}{\texttt{global.startup.events}
property} defines the event's default actions.
\textbf{Application Startup Events} runs once for each Site instance
Liferay DXP initializes. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Startup\%20Events}{\texttt{application.startup.events}
property} defines the event's default actions.
\section{Related Topics}\label{related-topics-9}
\href{/docs/7-2/customization/-/knowledge_base/c/waiting-on-lifecycle-events}{Waiting
on Lifecycle Events}
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi
Services and Dependency Injection with Declarative Services}
\chapter{The Benefits of Modularity}\label{the-benefits-of-modularity}
Dictionary.com defines
\href{http://www.dictionary.com/browse/modularity}{modularity} as
\emph{the use of individually distinct functional units, as in
assembling an electronic or mechanical system.} The distinct functional
units are called \emph{modules}.
NASA's Apollo spacecraft, for example, comprised three modules, each
with a distinct function:
\begin{itemize}
\tightlist
\item
\emph{Lunar Module}: Carried astronauts from the Apollo spacecraft to
the moon's surface and back.
\item
\emph{Service Module}: Provided fuel for propulsion, air conditioning,
and water.
\item
\emph{Command Module}: Housed the astronauts and communication and
navigation controls.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/modularity_apollo_spacecraft_diagram.png}
\caption{The Apollo spacecraft's modules collectively took astronauts to
the moon's surface and back to Earth.}
\end{figure}
The spacecraft and its modules exemplified these modularity
characteristics:
\begin{itemize}
\item
\textbf{Distinct functionality}: Each module provides a distinct
function (purpose); modules can be combined to provide an entirely new
collective function.
The Apollo spacecraft's modules were grouped together for a distinct
collective function: take astronauts from the Earth's atmospheric rim,
to the moon's surface, and back to Earth. The previous list identifies
each module's distinct function.
\item
\textbf{Dependencies}: Modules can require capabilities other modules
satisfy.
The Apollo modules had these dependencies:
\begin{itemize}
\item
Lunar Module depended on the Service Module to get near the moon.
\item
Command Module depended on the Service Module for power and oxygen.
\item
Service Module depended on the Command Module for instruction.
\end{itemize}
\item
\textbf{Encapsulation}: Modules hide their implementation details but
publicly define their capabilities and interfaces.
Each Apollo module was commissioned with a contract defining its
capabilities and interface, while each module's details were
encapsulated (hidden) from other modules. NASA integrated the modules
based on their interfaces.
\item
\textbf{Reusability}: A module can be applied to different scenarios.
The Command Module's structure and design were reusable. NASA used
different versions of the Command Module, for example, throughout the
Apollo program, and in the Gemini Program, which focused on Earth
orbit.
\end{itemize}
NASA used modularity to successfully complete over a dozen missions to
the moon. Can modularity benefit software too? Yes! The following
sections show you how:
\begin{itemize}
\tightlist
\item
\hyperref[modularity-benefits-for-software]{Modularity benefits for
software}
\item
\hyperref[example-designing-a-modular-application]{Example: How to
design a modular application}
\end{itemize}
\section{Modularity Benefits for
Software}\label{modularity-benefits-for-software}
Java applications have predominantly been monolithic: they're developed
in large code bases. In a monolith, it's difficult to avoid tight
coupling of classes. Modular application design, conversely, facilitates
loose coupling, making the code easier to maintain. It's much easier and
more fun to develop small amounts of cohesive code in modules. Here are
some key benefits of developing modular software.
\section{Distinct Functionality}\label{distinct-functionality}
It's natural to focus on developing one piece of software at a time. In
a module, you work on a small set of classes to define and implement the
module's function. Keeping scope small facilitates writing high quality,
elegant code. The more cohesive the code, the easier it is to test,
debug, and maintain. Modules can be combined to provide a new function,
distinguishable from each module's function.
\section{Encapsulation}\label{encapsulation}
A module encapsulates a function (capability). Module implementations
are hidden from consumers, so you can create and modify them as you
like. Throughout a module's lifetime, you can fix and improve the
implementation or swap in an entirely new one. You make the changes
behind the scenes, transparent to consumers. A module's contract defines
its capability and interface, making the module easy to understand and
use.
\section{Dependencies}\label{dependencies}
Modules have requirements and capabilities. The interaction between
modules is a function of the capability of one satisfying the
requirement of another and so on. Modules are published to artifact
repositories, such as Maven Central. Module versioning schemes let you
specify dependencies on particular module versions or version ranges.
\section{Reusability}\label{reusability}
Modules that do their job well are hot commodities. They're reusable
across projects, for different purposes. As you discover helpful
reliable modules, you'll use them again and again.
It's time to design a modular application.
\section{Example: Designing a Modular
Application}\label{example-designing-a-modular-application}
Application design often starts out simple but gets more complex as you
determine capabilities the application requires. If a third party
library already provides the capability, you can
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{deploy
it with your app}. You can otherwise implement the capability yourself.
As you design various aspects of your app to support its function, you
must decide how those aspects fit into the code base. Putting them in a
single monolithic code base often leads to tight coupling, while
designating separate modules for each aspect fosters loose coupling.
Adopting a modular approach to application design lets you reap the
modularity benefits.
For example, you can apply modular design to a speech recognition app.
Here are the app's function and required capabilities:
\emph{Function}: interface with users to translate their speech into
text for the computer to understand.
\emph{Required capabilities}:
\begin{itemize}
\tightlist
\item
Translates user words to text
\item
Uses a selected computer voice to speak to users.
\item
Interacts with users based on a script of instructions that include
questions, commands, requests, and confirmations.
\end{itemize}
You could create modules to provide the required capabilities:
\begin{itemize}
\tightlist
\item
\emph{Speech to text}: Translates spoken words to text the computer
understands.
\item
\emph{Voice UI}: Interacts with users based on stored questions,
commands, and confirmations.
\item
\emph{Instruction manager}: Stores and provides the application's
questions, commands, and confirmations.
\item
\emph{Computer voice}: Stores and provides computer voices for users
to choose from.
\end{itemize}
The following diagram contrasts a monolithic design for the speech
recognition application with a modular design.
\begin{figure}
\centering
\includegraphics{./images/modularity-benefits-application-design-example.png}
\caption{The speech recognition application can be implemented in a
single monolithic code base or in modules, each focused on a particular
function.}
\end{figure}
Designing the app as a monolith lumps everything together. There are no
initial boundaries between the application aspects, whereas the modular
design distinguishes the aspects.
Developers can create the modules in parallel, each one with its own
particular capability. Designing applications that comprise modules
fosters writing cohesive pieces of code that represent capabilities.
Each module's capability can potentially be \emph{reused} in other
scenarios too.
For example, the \emph{Instruction manager} and \emph{Computer voice}
modules can be \emph{reused} by a navigation app.
\begin{figure}
\centering
\includegraphics{./images/modularity-benefits-module-reuse.png}
\caption{The \emph{Instruction manager} and \emph{Computer voice}
modules designed for the speech recognition app can be used (or
\emph{reused}) by a navigation app.}
\end{figure}
Here are the benefits of designing the speech recognition app as
modules:
\begin{itemize}
\tightlist
\item
Each module represents a capability that contributes to the app's
overall function.
\item
The app depends on modules, that are easy to develop, test, and
maintain.
\item
The modules can be reused in different applications.
\end{itemize}
In conclusion, modularity has literally taken us to the moon and back.
It benefits software development too. The example speech recognition
application demonstrated how to design an app that comprises modules.
Next you'll learn how OSGi facilitates creating modules that provide and
consume services.
\chapter{OSGi and Modularity}\label{osgi-and-modularity}
Modularity makes writing software, especially as a team, fun! Here are
some benefits to modular development on DXP:
\begin{itemize}
\tightlist
\item
Liferay DXP's runtime framework is lightweight, fast, and secure.
\item
The framework uses the OSGi standard. If you have experience using
OSGi with other projects, you can apply your existing knowledge to
developing on DXP.
\item
Modules publish services to and consume services from a service
registry. Service contracts are loosely coupled from service providers
and consumers, and the registry manages the contracts automatically.
\item
Modules' dependencies are managed automatically by the container,
dynamically (no restart required).
\item
The container manages module life cycles dynamically. Modules can be
installed, started, updated, stopped, and uninstalled while Liferay is
running, making deployment a snap.
\item
Only a module's classes whose packages are explicitly exported are
publicly visible; OSGi hides all other classes by default.
\item
Modules and packages are semantically versioned and declare
dependencies on specific versions of other packages. This allows two
applications that depend on different versions of the same packages to
each depend on their own versions of the packages.
\item
Team members can develop, test, and improve modules in parallel.
\item
You can use your existing developer tools and environment to develop
modules.
\end{itemize}
There are many benefits to modular software development with OSGi, and
we can only scratch the surface here. Once you start developing modules,
you might find it hard to go back to developing any other way.
\section{Modules}\label{modules}
It's time to see what module projects look like and see Liferay DXP's
modular development features in action. To keep things simple, only
project code and structure are shown: you can
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{create
modules} like these anytime.
These modules collectively provide a command that takes a String and
uses it in a greeting. Consider it ``Hello World'' for modules.
\section{API}\label{api}
The API module is first. It defines the contract that a provider
implements and a consumer uses. Here is its structure:
\begin{itemize}
\tightlist
\item
\texttt{greeting-api}
\begin{itemize}
\tightlist
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/greeting/api}
\begin{itemize}
\tightlist
\item
\texttt{Greeting.java}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\end{itemize}
\end{itemize}
Very simple, right? Beyond the Java source file, there are only two
other files: a Gradle build script (though you can use any build system
you want), and a configuration file called \texttt{bnd.bnd}. The
\texttt{bnd.bnd} file describes and configures the module:
\begin{verbatim}
Bundle-Name: Greeting API
Bundle-SymbolicName: com.liferay.docs.greeting.api
Bundle-Version: 1.0.0
Export-Package: com.liferay.docs.greeting.api
\end{verbatim}
The module's name is \emph{Greeting API}. Its symbolic name--a name that
ensures uniqueness--is \texttt{com.liferay.docs.greeting.api}. Its
semantic version is declared next, and its package is \emph{exported},
which means it's made available to other modules. This module's package
is just an API other modules can implement.
Finally, there's the Java class, which in this case is an interface:
\begin{verbatim}
package com.liferay.docs.greeting.api;
import aQute.bnd.annotation.ProviderType;
@ProviderType
public interface Greeting {
public void greet(String name);
}
\end{verbatim}
The interface's \texttt{@ProviderType} annotation tells the service
registry that anything implementing the interface is a provider. The
interface's one method asks for a \texttt{String} and doesn't return
anything.
That's it! As you can see, creating modules is not very different from
creating other Java projects.
\section{Provider}\label{provider}
An interface only defines an API; to do something, it must be
implemented. This is what the provider module is for. Here's what a
provider module for the Greeting API looks like:
\begin{itemize}
\tightlist
\item
\texttt{greeting-impl}
\begin{itemize}
\tightlist
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/greeting/impl}
\begin{itemize}
\tightlist
\item
\texttt{GreetingImpl.java}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\end{itemize}
\end{itemize}
It has the same structure as the API module: a build script, a
\texttt{bnd.bnd} configuration file, and an implementation class. The
only differences are the file contents. The \texttt{bnd.bnd} file is a
little different:
\begin{verbatim}
Bundle-Name: Greeting Impl
Bundle-SymbolicName: com.liferay.docs.greeting.impl
Bundle-Version: 1.0.0
\end{verbatim}
The bundle name, symbolic name, and version are all set similarly to the
API.
Finally, there's no \texttt{Export-Package} declaration. A client (which
is the third module you'll create) just wants to use the API: it doesn't
care how its implementation works as long as the API returns what it's
supposed to return. The client, then, only needs to declare a dependency
on the API; the service registry injects the appropriate implementation
at runtime.
Pretty cool, eh?
All that's left, then, is the class that provides the implementation:
\begin{verbatim}
package com.liferay.docs.greeting.impl;
import com.liferay.docs.greeting.api.Greeting;
import org.osgi.service.component.annotations.Component;
@Component(
immediate = true,
property = {
},
service = Greeting.class
)
public class GreetingImpl implements Greeting {
@Override
public void greet(String name) {
System.out.println("Hello " + name + "!");
}
}
\end{verbatim}
The implementation is simple. It uses the \texttt{String} as a name and
prints a hello message. A better implementation might be to use
Liferay's API to collect all the names of all the users in the system
and send each user a greeting notification, but the point here is to
keep things simple. You should understand, though, that there's nothing
stopping you from replacing this implementation by deploying another
module whose Greeting implementation's \texttt{@Component} annotation
specifies a higher service ranking property (e.g.,
\texttt{"service.ranking:Integer=100"}).
This \texttt{@Component} annotation defines three options:
\texttt{immediate\ =\ true}, an empty property list, and the service
class that it implements. The \texttt{immediate\ =\ true} setting means
that this module should not be lazy-loaded; the service registry loads
it as soon as it's deployed, instead of when it's first used. Using the
\texttt{@Component} annotation declares the class as a Declarative
Services component, which is the most straightforward way to create
components for OSGi modules. A component is a POJO that the runtime
creates automatically when the module starts.
To compile this module, the API it's implementing must be on the
classpath. If you're using Gradle, you'd add the \texttt{greetings-api}
project to your \texttt{dependencies\ \{\ ...\ \}} block. In a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} module, the dependency looks like this:
\begin{verbatim}
compileOnly project (':modules:greeting-api')
\end{verbatim}
That's all there is to a provider module.
\section{Consumer}\label{consumer}
The consumer or client uses the API that the API module defines and the
provider module implements. DXP has many different kinds of consumer
modules.
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets} are
the most common consumer module type, but since they are a topic all by
themselves, this example stays simple by creating an command for the
Apache Felix Gogo shell. Note that consumers can, of course, consume
many different APIs to provide functionality.
A consumer module has the same structure as the other module types:
\begin{itemize}
\tightlist
\item
\texttt{greeting-command}
\begin{itemize}
\tightlist
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/greeting/command}
\begin{itemize}
\tightlist
\item
\texttt{GreetingCommand.java}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\end{itemize}
\end{itemize}
Again, you have a build script, a \texttt{bnd.bnd} file, and a Java
class. This module's \texttt{bnd.bnd} file is almost the same as the
provider's:
\begin{verbatim}
Bundle-Name: Greeting Command
Bundle-SymbolicName: com.liferay.docs.greeting.command
Bundle-Version: 1.0.0
\end{verbatim}
There's nothing new here: you declare the same things you declared for
the provider.
Your Java class has a little bit more going on:
\begin{verbatim}
package com.liferay.docs.greeting.command;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import com.liferay.docs.greeting.api.Greeting;
@Component(
immediate = true,
property = {
"osgi.command.scope=greet",
"osgi.command.function=greet"
},
service = Object.class
)
public class GreetingCommand {
public void greet(String name) {
Greeting greeting = _greeting;
greeting.greet(name);
}
@Reference
private Greeting _greeting;
}
\end{verbatim}
The \texttt{@Component} annotation declares the same attributes, but
specifies different properties and a different service. As in Java,
where every class is a subclass of \texttt{java.lang.Object} (even
though you don't need to specify it by default), in Declarative
Services, the runtime needs to know the type of class to register.
Because you're not implementing any particular type, your parent class
is \texttt{java.lang.Object}, so you must specify that class as the
service. While Java doesn't require you to specify \texttt{Object} as
the parent when you're creating a class that doesn't inherit anything,
Declarative Services does.
The two properties define a command scope and a command function. All
commands have a scope to define their context, as it's common for
multiple APIs to have similar functions, such as \texttt{copy} or
\texttt{delete}. These properties specify you're creating a command
called \texttt{greet} in a scope called \texttt{greet}. While you get no
points for imagination, this sufficiently defines the command.
Since you specified \texttt{osgi.command.function=greet} in the
\texttt{@Component} annotation, your class must have a method named
\texttt{greet}, and you do. But how does this \texttt{greet} method
work? It obtains an instance of the \texttt{Greeting} OSGi service and
invokes its \texttt{greet} method, passing in the \texttt{name}
parameter. How is an instance of the \texttt{Greeting} OSGi service
obtained? The \texttt{GreetingCommand} class declares a private service
bean, \texttt{\_greeting} of type \texttt{Greeting}. This is the OSGi
service type that the provider module registers. The \texttt{@Reference}
annotation tells the OSGi runtime to instantiate the service bean with a
service from the service registry. The runtime binds the
\texttt{Greeting} object of type \texttt{GreetingImpl} to the private
field \texttt{\_greeting}. The \texttt{greet} method uses the
\texttt{\_greeting} field value.
Just like the provider, the consumer needs to have the API on its
classpath in order to compile, but at runtime, since you've declared all
the dependencies appropriately, the container knows about these
dependencies, and provides them automatically.
If you were to
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploy
these modules to a DXP instance}, you'd be able to attach to the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
Shell} and execute a command like this:
\begin{verbatim}
greet:greet "Captain\ Kirk"
\end{verbatim}
The shell would then return your greeting:
\begin{verbatim}
Hello Captain Kirk!
\end{verbatim}
This most basic of examples should make it clear that module-based
development is easy and straightforward. The API-Provider-Consumer
contract fosters loose coupling, making your software easy to manage,
enhance, and support.
\section{A Typical Liferay
Application}\label{a-typical-liferay-application}
If you look at a typical application from Liferay's source, you'll
generally find at least four modules:
\begin{itemize}
\tightlist
\item
An API module
\item
A Service (provider) module
\item
A Test module
\item
A Web (consumer) module
\end{itemize}
This is exactly what you'll find for some smaller applications, like the
Mentions application that lets users mention other users with the
\texttt{@username} nomenclature in comments, blogs, or other
applications. Larger applications like the Documents and Media library
have more modules. In the case of the Documents and Media library, there
are separate modules for different document storage back-ends. In the
case of the Wiki, there are separate modules for different Wiki engines.
Encapsulating capability variations as modules facilitates
extensibility. If you have a document storage back-end that Liferay
doesn't yet support, you can implement Liferay's document storage API
for your solution by developing a module for it and thus extend
Liferay's Documents and Media library. If there's a Wiki dialect that
you like better than what Liferay's wiki provides, you can write a
module for it and extend Liferay's wiki.
Are you excited yet? Are you ready to start developing? Here are some
resources for you to learn more.
\section{Related Topics}\label{related-topics-10}
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio}
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Maven}
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{Upgrading
Code to 7.2}
\chapter{Module Lifecycle}\label{module-lifecycle}
In OSGi, all components, Java classes, resources, and descriptors are
deployed via modules. The \texttt{MANIFEST.MF} file describes the
module's physical characteristics, such as the packages it exports and
imports. The module's component description files specify its functional
characteristics (i.e., the services its components offer and consume).
Also modules and their components have their own lifecycles and
administrative APIs. Declarative Services and shell tools give you
fine-grained control over module and component deployment.
Since a module's contents depend on its activation, consider the
activation steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\emph{Installation}: Copying the module JAR into Liferay DXP's
\texttt{{[}Liferay\ Home{]}/deploy} folder installs the module to the
OSGi framework, marking the module \texttt{INSTALLED}.
\item
\emph{Resolution}: Once all the module's requirements are met (e.g.,
all packages it imports are available), the framework publishes the
module's exported packages and marks the module \texttt{RESOLVED}.
\item
\emph{Activation}: Modules are activated \emph{eagerly} by default.
That is, they're started in the framework and marked \texttt{ACTIVE}
on resolution. An active module's components are enabled. If a module
specifies a \texttt{lazy} activation policy, as shown in the manifest
header below, it's activated only after another module requests one of
its classes.
\begin{verbatim}
Bundle-ActivationPolicy: lazy
\end{verbatim}
\end{enumerate}
The figure below illustrates the module lifecycle.
\begin{figure}
\centering
\includegraphics{./images/module-state-diagram.png}
\caption{This state diagram illustrates the module lifecycle.}
\end{figure}
The
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Apache
Felix Gogo Shell} lets you manage the module lifecycle. You can
install/uninstall modules and start/stop them. You can update a module
and notify dependent modules to use the update. Liferay's tools,
including
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio DXP},
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}, and
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} offer
similar shell commands that use the OSGi Admin API.
On activating a module, its components are enabled. But only
\emph{activated} components can be used. Component activation requires
all its referenced services be satisfied. That is, all services it
references must be registered. The highest ranked service that matches a
reference is bound to the component. When the container finds and binds
all the services the component references, it registers the component.
It's now ready for activation.
Components can use \emph{delayed} (default) or \emph{immediate}
activation policies. To specify immediate activation, the developer adds
the attribute \texttt{immediate=true} to the \texttt{@Component}
annotation.
\begin{verbatim}
@Component(
immediate = true,
...
)
\end{verbatim}
Unless immediate activation is specified, the component's activation is
delayed. That is, the component's object is created and its classes are
loaded once the component is requested. In this way, delayed activation
can improve startup times and conserve resources.
Gogo Shell's
\href{http://felix.apache.org/documentation/subprojects/apache-felix-service-component-runtime.html\#shell-command}{Service
Component Runtime commands} let you manage components:
\begin{itemize}
\item
\texttt{scr:list\ {[}bundleID{]}}: Lists the module's (bundle's)
components.
\item
\texttt{scr:info\ {[}componentID\textbar{}fullClassName{]}}: Describes
the component, including its status and the services it provides.
\item
\texttt{scr:enable\ {[}componentID\textbar{}fullClassName{]}}: Enables
the component.
\item
\texttt{scr:disable\ {[}componentID\textbar{}fullClassName{]}}:
Disables the component. It's disabled on the server (or current server
node in a cluster) until the server is restarted.
\end{itemize}
Service references are static and reluctant by default. That is, an
injected service remains bound to the referencing component until the
service is disabled. Alternatively, you can specify \emph{greedy}
service policies for references. Every time a higher ranked matching
service is registered, the framework unbinds the lower ranked service
from the component (whose service policy is greedy) and binds the new
service in its place automatically. Here's a \texttt{@Reference}
annotation that uses a greedy policy:
\begin{verbatim}
@Reference(policyOption = ReferencePolicyOption.GREEDY)
\end{verbatim}
Declarative Services annotations let you specify component activation
and service policies. Gogo Shell commands let you control modules and
components.
\section{Related Topics}\label{related-topics-11}
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Creating
a Project}
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{Upgrading
Code to 7.2}
\chapter{UI Architecture}\label{ui-architecture}
\href{/docs/7-2/user/-/knowledge_base/u/the-liferay-distinction}{Liferay
DXP's UI} is a portal for adding sites, pages, widgets, and content. It
helps people do work,
\href{/docs/7-2/user/-/knowledge_base/u/collaboration}{collaborate}, and
\href{/docs/7-2/user/-/knowledge_base/u/web-experience-management}{share
content}.
The UI comprises the following parts:
\begin{itemize}
\item
Content: images, videos, and text.
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/application-development}{Applications}:
Widgets and portlets that expose functionality for accomplishing
tasks.
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes}:
Plugins that use CSS, FreeMarker templates, HTML, and JavaScript to
provide a site's overall look and feel.
\item
Product navigation sidebars and panels: Use these for administering
sites.
\end{itemize}
\section{Content}\label{content}
Liferay DXP's built-in applications help you publish images, video,
forms, markup text, and more to site pages.
\href{/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media}{Documents
and Media} stores images, videos, and documents to use throughout your
site. The
\href{/docs/7-2/user/-/knowledge_base/u/web-experience-management}{Web
Experience Management} suite helps you create, maintain, and organize
content. \href{/docs/7-2/user/-/knowledge_base/u/forms}{Liferay Forms}
gives you robust form building capability.
\href{/docs/7-2/user/-/knowledge_base/u/creating-forums-with-message-boards}{Message
Boards} facilitate lively discussions and
\href{/docs/7-2/user/-/knowledge_base/u/publishing-blogs}{Blogs} let
users express themselves with markup text and images. These are just a
few of the built-in applications for adding site content.
\section{Applications}\label{applications}
Liferay DXP applications provide content and help users accomplish
tasks. They're
\href{/7-2/appdev/-/knowledge_base/a/web-front-ends}{developed the same
way} as other web applications, and Liferay DXP can combine multiple
applications on one page.
Liferay DXP supports developing JavaScript-based applications using
popular front-end frameworks:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-an-angular-application}{Angular}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-react-application}{React}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-vue-application}{Vue}
\end{itemize}
Java-based portlet applications use the latest portlet standards and
frameworks, including ones familiar to experienced Liferay portlet
developers:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC Portlet}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/portletmvc4spring}{PortletMVC4Spring}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/jsf-portlet}{JSF Portlet}
\end{itemize}
In the UI, applications are referred to as Widgets and categorized for
users to add to pages. Administrative applications are available in the
product menu panels.
\begin{figure}
\centering
\includegraphics{./images/architecture-ui-widgets.png}
\caption{Widget pages offer users functionality. Widgets are organized
into a page template's rows and columns. This template has two columns:
a smaller left column and larger right column. On this page, users
select tags in the Tag Cloud widget and the matching tagged images show
the Asset Publisher widget.}
\end{figure}
\section{Themes}\label{themes}
A
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{theme}
styles a site with a unique look and feel. It's developed as a WAR
project that includes CSS, JavaScript, and markup content. You can
develop themes using whatever tools you prefer, but Liferay DXP offers
\href{https://getbootstrap.com/}{Bootstrap}-based components and
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-themes}{theme
tooling} to create and deploy themes in no time.
\begin{figure}
\centering
\includegraphics{./images/architecture-ui-themes.png}
\caption{You can select an attractive theme and apply it to your site.}
\end{figure}
Here's a quick demonstration of developing a theme:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a theme using the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Theme
Generator}. The theme extends the base theme you specified to the
Theme Generator---Liferay's
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-theme/frontend-theme-styled}{Styled
theme} is the default.
\item
Run
\href{https://portal.liferay.dev/docs/7-2/frameworks/-/knowledge_base/f/building-your-themes-files}{\texttt{gulp\ build}}
to generate the base theme files to the \texttt{build} folder
subfolders:
\begin{itemize}
\item
\texttt{templates}: FreeMarker templates specify site page markup.
\texttt{portal\_normal.ftl} is the central file; it includes
templates that define the page parts (e.g., header, navigation,
footer). The \texttt{init.ftl} file defines default variables
available to the templates.
\item
\texttt{css}: SCCS files that provide styling.
\item
\texttt{font}: Font Awesome and Glyphicons fonts.
\item
\texttt{js}: JavaScript files; \texttt{main.js} is the Styled
theme's JavaScript.
\item
\texttt{images}: Image files.
\end{itemize}
\item
Override aspects of the base theme by copying relevant files from the
\texttt{build} subfolders to folders of the same name in your
\texttt{src} folder. The
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide}{Theme
Anatomy Guide} describes all the files. Here's an example of a
customized \texttt{portal\_normal.ftl}:
\end{enumerate}
\begin{verbatim}
<#include "${full_templates_path}/footer.ftl" />
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
Add custom styling using your theme's \texttt{\_custom.scss} file
(i.e., \texttt{src/css/\_custom.scss}). Liferay DXP supports
\href{https://getbootstrap.com/}{Bootstrap}, as well as
\href{https://sass-lang.com/}{Sass}, so you can use Bootstrap
utilities in your markup and Sass nesting, variables, and more in your
CSS files. This snippet styles the logo:
\end{enumerate}
\begin{verbatim}
.logo {
margin-left: 15px;
img {
height: auto;
}
@include media-breakpoint-down(md) {
text-align: center;
width: 100%;
}
}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/architecture-ui-portal-dev-logo.png}
\caption{You can provide custom styling using the theme's
\texttt{\_custom.sccs} file.}
\end{figure}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
Deploy your theme by executing \texttt{gulp\ deploy}.
\end{enumerate}
The theme is available to
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{apply}
to your site.
For details,
\href{/docs/7-2/customization/-/knowledge_base/c/theme-components}{Theme
Components} breaks down a theme's parts, and the
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes
section} provides theme development details.
\section{Product Navigation Sidebars and
Panels}\label{product-navigation\noindent\hrulefills-and-panels}
The product navigation sidebars and panels enable administrators to
build sites, add pages, apply themes, and configure the portal. It's
also where you can provide administrative functionality for your custom
applications. The navigation sidebars and panels are customizable.
\begin{figure}
\centering
\includegraphics{./images/architecture-ui-menus-and-panel-app.png}
\caption{Liferay facilitates integrating custom administrative
functionality through navigation menus and administrative applications.}
\end{figure}
As you can see, Liferay DXP's UI is highly flexible and customizable.
Here's where to learn more:
\begin{itemize}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/theme-components}{Theme
Components}: Explains available mechanisms and extensions for
customizing and theming pages, content, and applications.
\item
\href{/docs/7-2/customization/-/knowledge_base/c/understanding-the-page-structure}{Understanding
the Page Structure}: Describes how the page's UI is organized and
introduces tools for populating and developing each section.
\end{itemize}
\chapter{Theme Components}\label{theme-components}
This guide provides an overview of the following theme development and
customization topics:
\begin{itemize}
\tightlist
\item
\hyperref[theme-templates]{Theme Templates}
\item
\hyperref[css-frameworks-and-extensions]{CSS Frameworks and
Extensions}
\item
\hyperref[theme-customizations-and-extensions]{Theme Customizations
and Extensions}
\item
\hyperref[portlet-customizations-and-extensions]{Portlet
Customizations and Extensions}
\end{itemize}
\section{Theme Templates and
Utilities}\label{theme-templates-and-utilities}
The default FreeMarker templates provide helpful utilities and handle
key pieces of page layout (page) functionality:
\begin{itemize}
\tightlist
\item
\texttt{portal\_normal.ftl}: Similar to a static site's
\texttt{index.html}, this file is the hub for all the theme templates
and provides the overall markup for the page.
\item
\texttt{init.ftl}: Contains variables commonly used throughout the
theme templates. Refer to it to look up theme objects. For
convenience, the
\href{/docs/7-2/reference/-/knowledge_base/r/freemarker-variable-reference-guide}{FreeMarker
Variable Reference Guide} lists the objects. \textbf{DO NOT override
this file}.
\item
\texttt{init\_custom.ftl}: Used to override FreeMarker variables in
\texttt{init.ftl} and to define new variables, such as
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings}{theme
settings}.
\item
\texttt{portlet.ftl}: Controls the theme's portlets. If your theme
uses
\href{/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets\#portlet-decorators}{Portlet
Decorators}, modify this file to create application decorator-specific
theme settings.
\item
\texttt{navigation.ftl}: Contains the navigation markup. To customize
pages in the navigation, you must use the
\texttt{liferay.navigation\_menu} macro. Then you can leverage
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/site-navigation/site-navigation-menu-web/src/main/resources/com/liferay/site/navigation/menu/web/portlet/template/dependencies}{widget
templates} for the navigation menu. Note that \texttt{navigation.ftl}
also defines the hamburger icon and \texttt{navbar-collapse} class
that provides the simplified navigation toggle for mobile viewports,
as shown in the snippet below for the Classic theme:
\end{itemize}
\begin{verbatim}
<#if has_navigation && is_setup_complete>
<@liferay.navigation_menu default_preferences="${preferences}" />
#if>
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-mobile-nav.png}
\caption{The collapsed navbar provides simplified user-friendly
navigation for mobile devices.}
\end{figure}
\begin{itemize}
\tightlist
\item
\texttt{portal\_pop\_up.ftl}: Controls pop up dialogs for the theme's
portlets. Similar to \texttt{portal\_normal.ftl},
\texttt{portal\_pop\_up.ftl} provides the markup template for all
pop-up dialogs, such as a portlet's Configuration menu. It also has
access to the FreeMarker variables defined in \texttt{init.ftl} and
\texttt{init\_custom.ftl}.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-theme-templates.png}
\caption{Each theme template provides a portion of the page's markup and
functionality.}
\end{figure}
\begin{itemize}
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-template/portal-template-freemarker/src/main/resources/FTL_liferay.ftl}{\texttt{FTL\_Liferay.ftl}}:
Provides
\href{/docs/7-2/reference/-/knowledge_base/r/product-freemarker-macros}{macros}
for commonly used portlets and theme resources.
\item
\texttt{taglib-mappings.properties}: Maps the portal taglibs to
FreeMarker macros. Taglibs can quickly create common UI components.
This properties file is provided separately for each app taglib. For
convenience, these FreeMarker macros appear in the
\href{/docs/7-2/reference/-/knowledge_base/r/freemarker-taglib-macros}{FreeMarker
Taglib Mappings reference guide}. See the
\href{/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs}{Taglib
reference} for more information on using each taglib in your theme
templates.
\end{itemize}
\section{CSS Frameworks and
Extensions}\label{css-frameworks-and-extensions}
Themes are integrated with \href{https://sass-lang.com/}{SASS}, so you
can take full advantage of Sass mixins, nesting, partials, and variables
in your CSS.
Also important to note is \href{https://clayui.com/}{Clay CSS}, the web
implementation of Liferay's \href{https://lexicondesign.io/}{Lexicon
design language}. An extension of Bootstrap, Clay CSS fills the gaps
between Bootstrap and the needs of Liferay DXP, providing additional
components and CSS patterns that you can use in your themes. Clay base,
Liferay's Bootstrap API extension, along with Atlas, a custom Bootstrap
theme, creates Liferay DXP's Classic theme. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-atlas-and-clay-base-themes}{Customizing
Atlas and Clay Base Themes} for more information.
\section{Theme Customizations and
Extensions}\label{theme-customizations-and-extensions}
The theme templates, along with the CSS, provide much of the overall
look and feel for the page, but additional extension
points/customizations are available. The following extensions and
mechanisms are available for themes:
\begin{itemize}
\tightlist
\item
\textbf{Color Schemes:} Specifies configurable color scheme settings
Administrators can configure via the Look and Feel menu. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-color-schemes-for-your-theme}{color
scheme tutorial} for more information.
\item
\textbf{Configurable Theme Settings:} Administrators can configure
theme aspects that change frequently, such as the visibility of
certain elements, changing a daily quote, etc. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings}{Configurable
Theme Settings tutorial} for more information.
\item
\textbf{Context Contributor:} Exposes Java variables and functionality
for use in FreeMarker templates. This allows non-JSP templating
languages in themes, widget templates, and any other templates. See
the
\href{/docs/7-2/frameworks/-/knowledge_base/f/injecting-additional-context-variables-and-functionality-into-your-theme-templates}{Context
Contributors tutorial} or more information.
\item
\textbf{Theme Contributor:} A package containing UI resources, not
attached to a theme, that you want to include on every page. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site}{Theme
Contributors tutorial} for more information.
\item
\textbf{Themelet:} Small, extendable, and reusable pieces of code
containing CSS and JavaScript. They can be shared with other
developers to provide common components for themes. See
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Generating
Themelets} for more information.
\end{itemize}
\section{Portlet Customizations and
Extensions}\label{portlet-customizations-and-extensions}
You can customize portlets with these mechanisms and extensions:
\begin{itemize}
\tightlist
\item
\textbf{Portlet FTL Customizations:} Customize the base template
markup for all portlets. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets}{Theming
Portlets} for more information.
\item
\textbf{Widget Templates:} Provides an alternate display style for a
portlet. Note that not all portlets support widget templates. See the
\href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Widget
Templates User Guide} for more information.
\item
\textbf{Portlet Decorator:} Customizes the exterior decoration for a
portlet. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets\#portlet-decorators}{Portlet
Decorators} for more information.
\item
\textbf{Web Content Template:} Defines how structures are displayed
for web content. See the
\href{/docs/7-2/user/-/knowledge_base/u/designing-web-content-with-templates}{Web
Content Templates User Guide articles} for more information.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-portlet-customizations.png}
\caption{There are several extension points for customizing portlets}
\end{figure}
\section{Related Topics}\label{related-topics-12}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/understanding-the-page-structure}{Understanding
the Page Structure}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Installing
the Theme Generator and Creating a Theme}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-themes}{Developing
Themes}
\end{itemize}
\chapter{Understanding the Page
Structure}\label{understanding-the-page-structure}
Understanding the page's structure is crucial to targeting the correct
markup for styling, organizing your content, and creating your site.
Your page layout is unique to the requirements and design for your site.
The Unstyled theme's default page layout is organized into three key
sections in its \texttt{portal\_normal.ftl} template:
\begin{itemize}
\tightlist
\item
\textbf{Header:} Contains the navigation, site logo and title (if
shown), and sign-in link when the user isn't logged in.
\item
\textbf{Main Content:} Contains the portlets or fragments for the
page.
\item
\textbf{Footer:} contains additional information, such as the
copyright or author.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-sections.png}
\caption{The page layout is broken into three key sections.}
\end{figure}
\section{Portlets or Fragments}\label{portlets-or-fragments}
The \texttt{\#content} \texttt{Section} makes up the majority of the
page. Portlets or fragments are contained inside the
\texttt{\#main-content} \texttt{div}. Liferay DXP ships with a default
set of applications that provide common functionality, such as forums
and Wikis, documents and media, blogs, and more. For more information on
using Liferay DXP and its native portlets, see the
\href{/documentation/user}{User \& Admin documentation}. You can also
create custom portlets for your site. Portlets can be added via the Add
Menu (referred to as widget), included in a sitemap through the
\href{/docs/7-2/frameworks/-/knowledge_base/f/importing-resources-with-a-theme}{Resources
Importer}, or they can be
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes}{embedded
in the page's theme}. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{portlet
tutorials section} for more information on creating and developing
portlets.
You can target the elements and IDs shown in the table below to style
the page:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Element
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{div} & \texttt{\#wrapper} & The container div for the page
contents \\
\texttt{header} & \texttt{\#banner} & The page's header \\
\texttt{section} & \texttt{\#content} \textgreater{}
\texttt{\#main-content} & The main contents of the page (portlets or
fragments) \\
\texttt{footer} & \texttt{\#footer} & The page's footer \\
\end{longtable}
\noindent\hrulefill
\begin{figure}
\centering
\includegraphics{./images/portal-layout-elements.png}
\caption{Each section of the page has elements and IDs that you can
target for styling.}
\end{figure}
As shown in the diagram above, you can also add
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{fragments}
to a page. Fragments are components---composed of CSS, JavaScript, and
HTML---that provide key pieces of functionality for the page (i.e.~a
carousel or banner). Liferay DXP provides an editor for creating
collections of fragments that you can then add to the page. These
fragments can be edited on the page to suit your vision.
\section{Layout Templates, Page Templates, and Site
Templates}\label{layout-templates-page-templates-and-site-templates}
The page layout within the \texttt{\#content} Section is determined by
the
\href{/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro}{Layout
Template}. Several layout templates are included out-of-the-box. You can
also
\href{/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro}{create
custom layout templates manually} or with the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Liferay
Theme Generator's layout sub-generator}.
Layout templates can be pre-configured depending on the
\href{/docs/7-2/user/-/knowledge_base/u/creating-pages}{page type} you
choose when the page is created. Along with setting the types of
portlets to include on the page, the page template may also define the
default layout template for the page. Climbing further up the scope
chain, you can create
\href{/docs/7-2/user/-/knowledge_base/u/building-sites-from-templates}{Site
Templates}, which can define the pages, page templates, layout
templates, and theme(s) to use for site pages.
\section{Product Navigation Sidebars and
Panels}\label{product-navigation-sidebars-and-panels-1}
The main page layout also contains a few notable sidebars an
administrative user can trigger through the Control Menu. These are
listed below:
\begin{itemize}
\tightlist
\item
\textbf{Add Menu:} For adding portlets (widgets) and fragments (if
applicable) to the page
\item
\textbf{Control Menu:} Provides the main navigation for accessing the
Add Menu, Product Menu, and Simulation Panel
\item
\textbf{Product Menu:} Contains administrative apps, configuration
settings, and user account settings, profile, and dashboard page
\item
\textbf{Simulation Panel:} Simulates how the page appears on different
devices
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-nav-control-menu.png}
\caption{Remember to account for the product navigation sidebars and
panels when styling your site.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-nav-add-menu.png}
\caption{The Add Menu pushes the main contents to the left.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-nav-product-menu.png}
\caption{The Product Menu pushes the main contents to the right.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/portal-layout-nav-simulation-panel.png}
\caption{The Simulation Panel pushes the main contents to the left.}
\end{figure}
When styling the page, you must keep the navigation menus in mind,
especially for absolutely positioned elements, such as a fixed navbar.
If the user is logged in and can view the Control Menu, the fixed navbar
must have a top margin equal to the Control Menu's height.
See the
\href{/docs/7-2/customization/-/knowledge_base/c/product-navigation}{Product
Navigation articles} for more information on customizing these menus.
\section{Related Topics}\label{related-topics-13}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Creating
Layout Templates with the Layouts Sub-generator}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/including-layout-templates-with-a-theme}{Bundling
Layout Templates with a Theme}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Installing
the Liferay Theme Generator and Creating a Theme}
\end{itemize}
\chapter{Bundle Classloading Flow}\label{bundle-classloading-flow}
The OSGi container searches several places for imported classes. It's
important to know where it looks and in what order. Liferay DXP's
classloading flow for OSGi bundles follows the OSGi Core specification.
It's straightforward, but complex. The figure below illustrates the flow
and this article walks you through it.
\begin{figure}
\centering
\includegraphics{./images/bundle-classloading-flow-chart.png}
\caption{This flow chart illustrates classloading in a bundle.}
\end{figure}
Here is the algorithm for classloading in a bundle:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
If the class is in a \texttt{java.*} package, delegate loading to the
parent classloader. Otherwise, continue.
\item
If the class is in the OSGi Framework's boot delegation list, delegate
loading to the parent classloader. Otherwise, continue.
\item
If the class is in one of the packages the bundle imports from a wired
exporter, the exporting bundle's classloader loads it. A \emph{wired
exporter} is another bundle's classloader that previously loaded the
package. If the class isn't found, continue.
\item
If the class is imported by one of the bundle's required bundles, the
required bundle's classloader loads it.
\item
If the class is in the bundle's classpath (manifest header
\texttt{Bundle-ClassPath}), the bundle's classloader loads it.
Otherwise, continue.
\item
If the class is in the bundle's fragments classpath, the bundle's
classloader loads it.
\item
If the class is in a package that's dynamically imported using
\texttt{DynamicImport-Package} and a wire is established with the
exporting bundle, the exporting bundle's classloader loads it.
Otherwise, the class isn't found.
\end{enumerate}
Congratulations! Now you know how Liferay DXP finds and loads classes
for OSGi bundles.
\chapter{Finding Extension Points}\label{finding-extension-points}
Liferay DXP provides many features that help users accomplish their
tasks. Sometimes, however, you may find it necessary to
\href{/docs/7-2/customization/-/knowledge_base/c/liferay-customization}{customize
a built-in feature}. It's easy to \textbf{find} an area you want to
customize, but it may seem like a daunting task to figure out
\textbf{how} to customize it. Liferay DXP was developed for easy
customization, meaning it has many extension points you can use to add
your own flavor.
There's a process you can follow that makes finding an extension point a
breeze.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Locate the bundle (module) that provides the functionality you want to
change.
\item
Find the components available in the module.
\item
Discover the extension points for the components you want to
customize.
\end{enumerate}
This article demonstrates finding an extension point. It steps through a
simple example that locates an extension point for importing LDAP users.
The example includes using Liferay DXP's
\href{/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps\#using-the-app-manager}{Application
Manager} and
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix
Gogo Shell}.
\section{Locate the Related Module and
Component}\label{locate-the-related-module-and-component}
First think of words that describe the application behavior you want to
change. The right keywords can help you easily track down the desired
module and its component. Consider the example for importing LDAP users.
Some candidate keywords for finding the component are \emph{import},
\emph{user}, \emph{security, }and \emph{LDAP}.
The easiest way to discover the module responsible for a particular
Liferay feature is to use the Application Manager. The Application
Manager lists apps and their included modules/components in an
easy-to-use interface. It even lists third party apps! You'll use your
keywords to target the applicable component.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the App Manager by navigating to \emph{Control Panel} →
\emph{Apps} → \emph{App Manager}. The top level lists independent apps
and independent modules.
\item
Navigate the apps and modules to find components that might provide
your desired extension point. Remember to check for your keywords in
element names. The keyword \emph{security} is found in the Liferay CE
Portal Security app. Select it.
\item
The Security application has several modules to inspect. Select the
\emph{Liferay Portal Security LDAP Implementation} module.
\begin{figure}
\centering
\includegraphics{./images/ldapimplementation-module.png}
\caption{The module name can be found using the App Manager.}
\end{figure}
\item
Search through the components, applying your keywords as a guide. Copy
the component name you think best fits the functionality you want to
customize; you'll inspect it later using the Gogo shell.
\begin{figure}
\centering
\includegraphics{./images/usermodellistener-component.png}
\caption{The component name can be found using the App Manager.}
\end{figure}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** When using the Gogo shell later, understand that it can take
several tries to find the component you're looking for; Liferay's naming
conventions facilitate finding extension points in a manageable time frame.
\end{verbatim}
\noindent\hrulefill
Next, you'll use the Gogo shell to inspect the component for extension
points.
\section{Finding Extension Points in a
Component}\label{finding-extension-points-in-a-component}
Once you have the component that relates to the functionality you want
to extend, you can use the Gogo shell's Service Component Runtime (SCR)
commands to inspect it. You can execute SCR commands using
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Liferay Blade
CLI} or in
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell}. This article assumes you're using the Gogo shell.
Execute the following command:
\begin{verbatim}
scr:info [COMPONENT_NAME]
\end{verbatim}
For the LDAP example component you copied previously, the command would
look like this:
\begin{verbatim}
scr:info com.liferay.portal.security.ldap.internal.messaging.UserImportMessageListener
\end{verbatim}
The output includes a lot of information. For this exercise, you're
interested in services the component references. They are extension
points. For example, here's the reference for the service that imports
LDAP users:
\begin{verbatim}
- _ldapUserImporter:
com.liferay.portal.security.ldap.exportimport.LDAPUserImporter
SATISFIED
1..1
dynamic+greedy
target=(*) scope=bundle (1 binding):
* Bound to [7764] from bundle 1754 (com.liferay.portal.security.ldap.impl:2.0.4)
\end{verbatim}
The \texttt{LDAPUserImporter} is the extension point for customizing the
LDAP user import process! If none of the references satisfy what you're
looking for, search other components from the App Manager.
If you plan on overriding the referenced service, you'll need to
understand the reference's policy and policy option. In the example, the
policy is \texttt{dynamic} and the policy option is \texttt{greedy}. If
the policy is \texttt{static} and the policy option is
\texttt{reluctant}, binding a new higher ranking service in place of a
bound service requires reactivating the component or changing the
target. For information on the other policies and policy options, visit
the \href{https://osgi.org/download/r6/osgi.enterprise-6.0.0.pdf}{OSGi
specification}, in particular, sections 112.3.5 and 112.3.6. See
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-osgi-services}{Overriding
OSGi Services} to learn how to override a component's service reference.
\textbf{Important} Not all Liferay extension points are available as
referenced services. Service references are common in Declarative
Services (DS) components, but extension points can be exposed in other
ways too. Here's a brief list of other potential extension points in
Liferay DXP:
\begin{itemize}
\tightlist
\item
Instances of
\texttt{org.osgi.util.tracker.ServiceTracker\textless{}S,\ T\textgreater{}}
\item
Uses of Liferay's \texttt{Registry.getServiceTracker}
\item
Uses of Liferay's \texttt{ServiceTrackerMap} or
\texttt{ServiceTrackerCollection}
\item
Any other component framework or whiteboard implementation (e.g.,
HTTP, JAX-RS) that supports tracking services; Blueprint, Apache
Dependency Manager, etc. could also introduce extension points.
\end{itemize}
There you have it! In the App Manager, you used keywords to find the
module component whose behavior you wanted to change. Then you used Gogo
shell to find the component extension point for implementing your
customization.
\chapter{Troubleshooting
Customizations}\label{troubleshooting-customizations}
When coding on any platform, you can sometimes run into issues that have
no clear resolution. This can be particularly frustrating. If you have
issues building, deploying, or running apps and modules, you want to
resolve them fast. These frequently asked questions and answers help you
troubleshoot and correct problems.
Click a question to view the answer.
{Why aren't my fragment's JSP overrides showing?~{}}
\begin{verbatim}
Make sure your Fragment-Host's bundle version is compatible with the host's bundle version .
\end{verbatim}
{Why doesn't the package I use from the fragment host resolve?~{}}
\begin{verbatim}
Refrain from importing (Import-Package: ...) host packages that the host doesn't export .
\end{verbatim}
\phantomsection\label{cacheable-web-content-taglibs}
{Why does my web content break when I refresh the page?~{}}
\begin{verbatim}
Some taglibs, such as the liferay-map taglib, have limitations when used in a cacheable template (e.g., FreeMarker and Velocity). For instance, when the liferay-map taglib is used in a cacheable template and the user refreshes the page, the map does not show.
One possible workaround is to disable cache for the template by editing it and unchecking the cacheable option. Alternatively, you can disable cache for all templates by navigating to System Settings→Template Engines and setting Resource Modification Check to 0.
As best practice, however, we recommend that you don't use taglibs in cacheable web content.
\end{verbatim}
\chapter{Why doesn't the package I use from the fragment host
resolve?}\label{why-doesnt-the-package-i-use-from-the-fragment-host-resolve}
An OSGi fragment can access all of the fragment host's packages---it
doesn't need to import them from another bundle. bnd adds external
packages the fragment uses (even ones in the fragment host) to the
fragment's \texttt{Import-Package:\ {[}package{]},...} OSGi manifest
header. That's fine for packages exported to the OSGi runtime. The
problem is, however, when bnd tries to import a host's internal package
(a package the host doesn't export). The OSGi runtime can't activate the
fragment because the internal package remains an
\texttt{Unresolved\ requirement}---a fragment shouldn't import a
fragment host's packages.
Resolve the issue by explicitly excluding host packages that the host
doesn't export.
For example, this fragment bundle's JSP uses classes from the fragment
host bundle's internal package
\texttt{com.liferay.portal.search.web.internal.custom.facet.display.context}:
\begin{verbatim}
<%@
page import="com.liferay.portal.search.web.internal.custom.facet.display.context.CustomFacetDisplayContext" %><%@
page import="com.liferay.portal.search.web.internal.custom.facet.display.context.CustomFacetTermDisplayContext" %>
\end{verbatim}
Since the example host bundle doesn't export the package, the fragment
bundle can avoid importing the package by using an OSGi manifest header,
like the one below, to explicitly exclude the package from package
imports:
\begin{verbatim}
Import-Package: !com.liferay.portal.search.web.internal.*,*
\end{verbatim}
\chapter{Why Aren't JSP overrides I Made Using Fragments
Showing?}\label{why-arent-jsp-overrides-i-made-using-fragments-showing}
\noindent\hrulefill
\textbf{Important:} It's strongly recommended to
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps}{customize
JSPs using Liferay DXP's API}. Since overriding a JSP using an OSGi
fragment is not based on APIs there's no way to guarantee that it will
fail gracefully. Instead, if your customization 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. Overriding a JSP using a fragment should only be used as a
last resort.
\noindent\hrulefill
The fragment module must specify the exact version of the host module. A
Liferay DXP upgrade might have changed some JSPs in the host module,
prompting a version update. If this occurs, check that your JSP
customizations are compatible with the updated host JSPs and then update
your fragment module's targeted version to match the host module.
For example, this \texttt{bnd.bnd} file from a fragment module uses
\texttt{Fragment-Host} to specify the host module and host module
version:
\begin{verbatim}
Bundle-Name: custom-login-jsp
Bundle-SymbolicName: custom.login.jsp
Bundle-Version: 1.0.0
Fragment-Host: com.liferay.login.web;bundle-version="1.1.18"
\end{verbatim}
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts}{Finding
versions of deployed modules} is straightforward.
\section{Related Topics}\label{related-topics-14}
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{JSP
Overrides using Portlet Filters}
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps}{Customizing
JSPs}
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts}{Finding
Artifacts}
\chapter{Using OSGi Services from EXT
Plugins}\label{using-osgi-services-from-ext-plugins}
\href{/docs/7-2/frameworks/-/knowledge_base/f/service-trackers-for-osgi-services}{\texttt{ServiceTrackers}}
are the best way for Ext plugins to access OSGi services. They account
for the possibility of OSGi services coming and going.
\section{Related Topics}\label{related-topics-15}
\href{/docs/7-2/appdev/-/knowledge_base/a/detecting-unresolved-osgi-components}{Detecting
Unresolved OSGi Components}
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix
Gogo Shell}
\chapter{Contributing to Liferay
Portal}\label{contributing-to-liferay-portal}
Liferay Portal is developed by its community consisting of users,
enthusiasts, employees, customers, partners, and others. We strongly
encourage you to contribute to Liferay's open source projects by
implementing new features, enhancing existing features, and fixing bugs.
We also welcome your participation in our forums, chat, writing
documentation, and translating existing documentation.
Liferay Portal is known for its innovative top quality features. To
maintain this reputation, all code changes are reviewed by a core set of
project maintainers. We encourage you to join our
\href{https://liferay-community.slack.com}{Slack Chat} and introduce
yourself to the core maintainer(s) and engage them as you contribute to
the areas they maintain.
Developing features and fixes requires cloning the source tree and
building Liferay Portal.
\section{Building Liferay Portal from
source}\label{building-liferay-portal-from-source}
The first step to contributing to Liferay Portal is to clone the
\texttt{liferay-portal} repo from GitHub and build the platform from
source code.
Please follow the instructions for
\href{https://portal.liferay.dev/participate/fix-a-bug/building-liferay-source}{building
Liferay Portal from source code}.
To better understand the code structure, please also read
\href{https://portal.liferay.dev/participate/fix-a-bug/how-the-source-is-organized}{How
the source is organized}.
\section{Tooling}\label{tooling}
\href{/docs/7-2/reference/-/knowledge_base/r/tooling}{Liferay tooling}
facilitates creating customizations and debugging code. Consider using
these Liferay development tools:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}: a
command line interface used to build and manage Liferay Workspaces and
Liferay Portal projects. This CLI is intended for Gradle or Maven
development.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}: a generated Gradle/Maven environment built to hold and
manage Liferay Portal projects.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio}: an Eclipse-based IDE supporting development for Liferay
Portal.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/intellij}{Liferay
IntelliJ Plugin}: a plugin providing support for Liferay Portal
development with IntelliJ IDEA.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Liferay
Theme Generator}: a generator that creates themes, layouts templates,
and themelets for Liferay Portal development.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/js-generator}{Liferay JS
Generator}: a generator that creates JavaScript portlets with
JavaScript tooling.
\end{itemize}
The
\href{https://portal.liferay.dev/participate/fix-a-bug/ide-support}{Configure
an IDE for use with the Liferay Source} page, explains how to set up the
project in your favorite IDE.
\section{Additional Resources}\label{additional-resources}
\href{https://liferay.dev}{Liferay Community Site}
\href{https://liferay-community.slack.com/}{Liferay Community Slack
Chat}
\href{https://liferay.dev/chat}{Liferay Community Slack Chat Self
Invite}
\href{https://www.liferay.com/legal/contributors-agreement}{Contributor
License Agreement}
\href{http://help.github.com/}{General GitHub documentation}
\href{http://help.github.com/send-pull-requests/}{GitHub pull request
documentation}
\chapter{Model Listeners}\label{model-listeners}
Model Listeners implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/ModelListener.html}{\texttt{ModelListener}
interface}. They are used to listen for persistence events on models and
do something in response (either before or after the event).
Model listeners are designed to perform lightweight actions in response
to a \texttt{create}, \texttt{remove}, or \texttt{update} attempt on an
entity's database table or a mapping table (for example,
\texttt{users\_roles}). Here are some supported use cases:
\begin{itemize}
\tightlist
\item
Audit Listener: In a separate database, record information about
updates to an entity's database table.
\item
Cache Clearing Listener: Clear caches that you've added to improve the
performance of custom code.
\item
Validation Listener: Perform additional validation on a model's
attribute values before they are persisted to the database.
\item
Entity Update Listener: Do some additional processing when an entity
table is updated. For example, notify users when changes are made to
their account.
\end{itemize}
There are also use cases that are not recommended, since they're likely
to break unpredictably and give you headaches:
\begin{itemize}
\tightlist
\item
Setting a model's attributes in an \texttt{onBeforeUpdate} call. If
some other database table has already been updated with the values
before your model listener is invoked, your database gets out of sync.
To change how an entity's attributes are set, consider using a
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers}{service
wrapper} instead.
\item
Wrapping a model. Model listeners are not called when fetching records
from the database.
\item
Creating worker threads to do parallel processing and querying data
you updated via your listener. Model listeners are called
\emph{before} the database transaction is complete (even the
\texttt{onAfter...} methods), so the queries could be executed before
the database transaction completes.
\end{itemize}
If there is no existing listener on the model, your model listener is
the only one that runs. However, there can be multiple listeners on a
single model, and the order in which the listeners run cannot be
controlled.
You can create a model listener in a module by doing two simple things:
\begin{itemize}
\tightlist
\item
Implement \texttt{ModelListener}
\item
Register the service in Liferay's OSGi runtime
\end{itemize}
\section{Creating a Model Listener
Class}\label{creating-a-model-listener-class}
Create a \texttt{-ModelListener} class that extends the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/BaseModelListener.html}{\texttt{BaseModelListener}
class}.
\begin{verbatim}
package ...;
import ...;
public class CustomEntityListener extends BaseModelListener {
// Override one or more methods from the ModelListener interface.
}
\end{verbatim}
In the body of the class, override any methods from the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/ModelListener.html}{\texttt{ModelListener}
interface}. The available methods are listed and described at the end of
this article.
In your model listener class, the parameterized type (for example,
\texttt{CustomEntity} in the snippet above) tells the listener's
\texttt{ServiceTrackerCustomizer} which model class to register the
listener against.
\section{Register the Model Listener
Service}\label{register-the-model-listener-service}
Register the service with Liferay's OSGi runtime for immediate
activation. If using Declarative Services, set
\texttt{service=\ ModelListener.class} and \texttt{immediate=true} in
the Component:
\begin{verbatim}
@Component(
immediate = true,
service = ModelListener.class
)
\end{verbatim}
That's all there is to preparing a model listener. Now learn what model
events you can respond to.
\section{Listening For Persistence
Events}\label{listening-for-persistence-events}
The \texttt{ModelListener} interface provides lots of opportunity to
listen for model events:
\begin{itemize}
\tightlist
\item
\textbf{\texttt{onAfterAddAssociation}:} If there's an association
between two models (if they have a mapping table), use this method to
do something after an association record is added.
\item
\textbf{\texttt{onAfterCreate}:} Use this method to do something after
the persistence layer's \texttt{create} method is called.
\item
\textbf{\texttt{onAfterRemove}:} Use this method to do something after
the persistence layer's \texttt{remove} method is called.
\item
\textbf{\texttt{onAfterRemoveAssociation}:} If there's an association
between two models (if they have a mapping table), do something after
an association record is removed.
\item
\textbf{\texttt{onAfterUpdate}:} Use this method to do something after
the persistence layer's \texttt{update} method is called.
\item
\textbf{\texttt{onBeforeAddAssociation}:} If there's an association
between two models (if they have a mapping table), do something before
an addition to the mapping table.
\item
\textbf{\texttt{onBeforeCreate}:} Use this method to do something
before the persistence layer's \texttt{create} method is called.
\item
\textbf{\texttt{onBeforeRemove}:} Use this method to do something
before the persistence layer's \texttt{remove} method is called.
\item
\textbf{\texttt{onBeforeRemoveAssociation}:} If there's an association
between two models (if they have a mapping table), do something before
a removal from the mapping table.
\item
\textbf{\texttt{onBeforeUpdate}:} Use this method to do something
before the persistence layer's \texttt{update} method is called.
\end{itemize}
Look in Liferay source file
\texttt{portal-kernel/src/com/liferay/portal/kernel/service/persistence/impl/BasePersistenceImpl.java},
particularly the \texttt{remove} and \texttt{update} methods, and you'll
see how model listeners are accounted for before (for the
\texttt{onBefore...} case) and after (for the \texttt{onAfter...} case)
the model persistence event.
Now that you know how to create model listeners, keep in mind that
they're useful as standalone projects or inside of your application. If
your application needs to do something (like add a custom entity) every
time a User is added in Liferay, you can include the model listener
inside your application.
\section{Related Topics}\label{related-topics-16}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-model-listener-hooks}{Upgrading
Model Listener Hooks}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder Persistence}
\end{itemize}
\chapter{Customizing JSPs}\label{customizing-jsps}
There are several different ways to customize JSPs in portlets and the
core. Liferay DXP's API provides the safest ways to customize them. If
you customize a JSP by other means, new versions of the JSP can render
your customization invalid and leave you with runtime errors. It's
highly recommended to use one of the API-based ways.
\section{Using Liferay DXP's API to Override a
JSP}\label{using-liferay-dxps-api-to-override-a-jsp}
Here are API-based approaches to overriding JSPs in Liferay DXP:
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2857}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3571}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3571}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
\textbf{Approach}
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
\textbf{Description}
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
\textbf{Cons/Limitations}
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes}{Dynamic
includes} & Adds content at dynamic include tags. & Limited to JSPs that
have \texttt{dynamic-include} tags (or tags whose classes inherit from
\texttt{IncludeTag}). Only inserts content in the JSPs at the dynamic
include tags. \\
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{Portlet
filters} & Modifies portlet requests and/or responses to simulate a JSP
customization. & Although this approach doesn't directly customize a
JSP, it achieves the effect of a JSP customization. \\
\end{longtable}
\section{Overriding a JSP Without Using Liferay DXP's
API}\label{overriding-a-jsp-without-using-liferay-dxps-api}
It's strongly recommended to customize JSPs using Liferay DXP's API, as
the previous section describes. Since overriding a JSP using an OSGi
fragment or a Custom JSP Bag is not based on APIs there's no way to
guarantee that they'll fail gracefully. Instead, if your customization
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. These approaches should only be used as a
last resort.
If you're maintaining a JSP customization that uses one of these
approaches, you should know how they work. This section describes them
and links to their tutorials.
Here are ways to customize JSPs without using Liferay DXP's API:
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2857}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3571}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3571}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
\textbf{Approach}
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
\textbf{Description}
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
\textbf{Cons/Limitations}
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-osgi-fragments}{OSGi
fragment} & Completely overrides a module's JSP using an OSGi fragment &
Changes to the original JSP or module can cause runtime errors. \\
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-custom-jsp-bag}{Custom
JSP bag} & Completely override a Liferay DXP core JSP or one of its
corresponding \texttt{-ext.jsp} files. & For Liferay DXP core JSPs only.
Changes to the original JSP or module can cause runtime errors. \\
\end{longtable}
All the JSP customization approaches are available to you. It's time to
customize some JSPs!
\chapter{Customizing JSPs with Dynamic
Includes}\label{customizing-jsps-with-dynamic-includes}
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-util/dynamic-include.html}{\texttt{liferay-util:dynamic-include}
tag} is placeholder into which you can inject content. Every JSP's
dynamic include tag is an extension point for inserting content (e.g.,
JavaScript code, HTML, and more). To do this, create a module that has
content you want to insert, register that content with the dynamic
include tag, and deploy your module.
\noindent\hrulefill
\textbf{Note}: If the JSP you want to customize has no
\texttt{liferay-util:dynamic-include} tags (or tags whose classes
inherit from \texttt{IncludeTag}), you must use a different
customization approach, such as
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{portlet
filters}.
\noindent\hrulefill
Blogs entries contain a good example of how dynamic includes work. For
reference, you can download the
\href{https://portal.liferay.dev/documents/113763090/114000186/example-dynamic-include-blogs-master.zip}{example
module}.
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Find the \texttt{liferay-util:dynamic-include} tag where you want to
insert content and note the tag's key.
The Blogs app's \texttt{view\_entry.jsp} has a dynamic include tag at
the top and another at the very bottom.
\begin{verbatim}
<%@ include file="/blogs/init.jsp" %>
... JSP content is here
\end{verbatim}
Here are the Blogs view entry dynamic include keys:
\begin{itemize}
\tightlist
\item
\texttt{key="com.liferay.blogs.web\#/blogs/view\_entry.jsp\#pre"}
\item
\texttt{key="com.liferay.blogs.web\#/blogs/view\_entry.jsp\#post"}
\end{itemize}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a module} (e.g., \texttt{blade\ create\ my-dynamic-include}). The
module will hold your dynamic include implementation.
\item
Specify compile-only dependencies, like these Gradle dependencies, in
your module build file:
\begin{verbatim}
dependencies {
compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
compileOnly group: "com.liferay", name: "com.liferay.petra.string", version: "1.0.0"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
compileOnly group: "org.osgi", name: "osgi.cmpn", version: "6.0.0"
}
\end{verbatim}
\item
Create an OSGi component class that implements the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/servlet/taglib/DynamicInclude.html}{\texttt{DynamicInclude}
interface}.
Here's an example dynamic include implementation for Blogs:
\begin{verbatim}
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.osgi.service.component.annotations.Component;
import com.liferay.portal.kernel.servlet.taglib.DynamicInclude;
@Component(
immediate = true,
service = DynamicInclude.class
)
public class BlogsDynamicInclude implements DynamicInclude {
@Override
public void include(
HttpServletRequest request, HttpServletResponse response,
String key)
throws IOException {
PrintWriter printWriter = response.getWriter();
printWriter.println(
"Added by Blogs Dynamic Include! ");
}
@Override
public void register(DynamicIncludeRegistry dynamicIncludeRegistry) {
dynamicIncludeRegistry.register(
"com.liferay.blogs.web#/blogs/view_entry.jsp#pre");
}
}
\end{verbatim}
Giving the class an \texttt{@Component} annotation that has the
service attribute \texttt{service\ =\ DynamicInclude.class} makes the
class a \texttt{DynamicInclude} service component.
\begin{verbatim}
@Component(
immediate = true,
service = DynamicInclude.class
)
\end{verbatim}
In the \texttt{include} method, add your content. The example
\texttt{include} method writes a heading.
\begin{verbatim}
@Override
public void include(
HttpServletRequest request, HttpServletResponse response,
String key)
throws IOException {
PrintWriter printWriter = response.getWriter();
printWriter.println(
"Added by Blogs Dynamic Include! ");
}
\end{verbatim}
In the \texttt{register} method, specify the dynamic include tag to
use. The example register method targets the dynamic include at the
top of the Blogs \texttt{view\_entry.jsp}.
\begin{verbatim}
@Override
public void register(DynamicIncludeRegistry dynamicIncludeRegistry) {
dynamicIncludeRegistry.register(
"com.liferay.blogs.web#/blogs/view_entry.jsp#pre");
}
\end{verbatim}
\end{enumerate}
Once you've
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deployed
your module}, the JSP dynamically includes your content. Congratulations
on injecting dynamic content into a JSP!
\chapter{JSP Overrides Using Portlet
Filters}\label{jsp-overrides-using-portlet-filters}
Portlet filters let you intercept portlet requests before they're
processed and portlet responses after they're processed but before
they're sent back to the client. You can operate on the request and / or
response to modify the JSP content. Unlike dynamic includes, portlet
filters give you access to all the content sent back to the client.
This demonstration uses a portlet filter to modify content in Liferay's
Blogs portlet. For reference, you can download the
\href{https://portal.liferay.dev/documents/113763090/114000186/example-portlet-filter-customize-jsp-master.zip}{example
module}.
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a new module and make sure it specifies these compile-only
dependencies, shown here in Gradle format:
\begin{verbatim}
dependencies {
compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
compileOnly group: "org.osgi", name: "osgi.cmpn", version: "6.0.0"
}
\end{verbatim}
\item
Create an OSGi component class that implements the
\texttt{javax.portlet.filter.RenderFilter} interface.
Here's an example portlet filter implementation for Blogs:
\begin{verbatim}
import java.io.IOException;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.filter.FilterChain;
import javax.portlet.filter.FilterConfig;
import javax.portlet.filter.PortletFilter;
import javax.portlet.filter.RenderFilter;
import javax.portlet.filter.RenderResponseWrapper;
import org.osgi.service.component.annotations.Component;
import com.liferay.portal.kernel.util.PortletKeys;
@Component(
immediate = true,
property = {
"javax.portlet.name=" + PortletKeys.BLOGS
},
service = PortletFilter.class
)
public class BlogsRenderFilter implements RenderFilter {
@Override
public void init(FilterConfig config) throws PortletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(RenderRequest request, RenderResponse response, FilterChain chain)
throws IOException, PortletException {
RenderResponseWrapper renderResponseWrapper = new BufferedRenderResponseWrapper(response);
chain.doFilter(request, renderResponseWrapper);
String text = renderResponseWrapper.toString();
if (text != null) {
String interestingText = " = 0) {
String newText1 = text.substring(0, index);
String newText2 = "\nAdded by Blogs Render Filter!
\n";
String newText3 = text.substring(index);
String newText = newText1 + newText2 + newText3;
response.getWriter().write(newText);
}
}
}
}
\end{verbatim}
\item
Make your class a \texttt{PortletFilter} service component by giving
it the \texttt{@Component} annotation that has the service attribute
\texttt{service\ \ \ \ \ \ =\ PortletFilter.class}. Target the portlet
whose content you're overriding by assigning it a javax.portlet.name
property that's the same as your portlet's key. Here's the example
\texttt{@Component} annotation:
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + PortletKeys.BLOGS
},
service = PortletFilter.class
)
\end{verbatim}
\item
Override the \texttt{doFilterMethod} to operate on the request or
response to produce the content you want. The example appends a
paragraph stating \texttt{Added\ \ \ \ \ \ by\ Blogs\ Render\ Filter!}
to the portlet content:
\begin{verbatim}
@Override
public void doFilter(RenderRequest request, RenderResponse response, FilterChain chain)
throws IOException, PortletException {
RenderResponseWrapper renderResponseWrapper = new BufferedRenderResponseWrapper(response);
chain.doFilter(request, renderResponseWrapper);
String text = renderResponseWrapper.toString();
if (text != null) {
String interestingText = " = 0) {
String newText1 = text.substring(0, index);
String newText2 = "\nAdded by Blogs Render Filter!
\n";
String newText3 = text.substring(index);
String newText = newText1 + newText2 + newText3;
response.getWriter().write(newText);
}
}
}
\end{verbatim}
The example uses a \texttt{RenderResponseWrapper} extension class
called \texttt{BufferedRenderResponseWrapper}.
\texttt{BufferedRenderResponseWrapper} is a helper class whose
\texttt{toString} method returns the current response text and whose
\texttt{getWriter} method lets you write data to the response before
it's sent back to the client.
\begin{verbatim}
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.portlet.RenderResponse;
import javax.portlet.filter.RenderResponseWrapper;
public class BufferedRenderResponseWrapper extends RenderResponseWrapper {
public BufferedRenderResponseWrapper(RenderResponse response) {
super(response);
charWriter = new CharArrayWriter();
}
public OutputStream getOutputStream() throws IOException {
if (getWriterCalled) {
throw new IllegalStateException("getWriter already called");
}
getOutputStreamCalled = true;
return super.getPortletOutputStream();
}
public PrintWriter getWriter() throws IOException {
if (writer != null) {
return writer;
}
if (getOutputStreamCalled) {
throw new IllegalStateException("getOutputStream already called");
}
getWriterCalled = true;
writer = new PrintWriter(charWriter);
return writer;
}
public String toString() {
String s = null;
if (writer != null) {
s = charWriter.toString();
}
return s;
}
protected CharArrayWriter charWriter;
protected PrintWriter writer;
protected boolean getOutputStreamCalled;
protected boolean getWriterCalled;
}
\end{verbatim}
\end{enumerate}
Once you've
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deployed
your module}, the portlet's JSP shows your custom content.
Your portlet filter operates directly on portlet response content.
Unlike dynamic includes, portlet filters let you work with all of a
JSP's content.
\chapter{JSP Overrides Using OSGi
Fragments}\label{jsp-overrides-using-osgi-fragments}
You can completely override JSPs using OSGi fragments. This approach is
powerful but can make things unstable when the host module is upgraded:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
By overriding an entire JSP, you might not account for new content or
new widgets essential to new host module versions.
\item
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).
\item
Liferay cannot guarantee that JSPs overridden by fragments can be
upgraded.
\end{enumerate}
Using OSGi fragments to override JSPs is a bad practice, equivalent to
using Ext plugins to customize Liferay DXP. They should only be used as
a last resort. Liferay's API based approaches to overriding JSPs (i.e.,
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes}{Dynamic
Includes} and
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{Portlet
Filters}), on the other hand, provide more stability as they customize
specific parts of JSPs that are safe to override. Also, the API based
approaches don't limit your override to a specific host module version.
If you are maintaining existing JSP overrides that use OSGi fragments,
however, this tutorial explains how they work.
An OSGi fragment that overrides a JSP requires these two things:
\begin{itemize}
\item
The host module's symbolic name and version in the OSGi header
\texttt{Fragment-Host} declaration.
\item
The original JSP with any modifications you need to make.
\end{itemize}
For more information about fragment modules, you can refer to section
3.14 of the
\href{https://osgi.org/specification/osgi.core/7.0.0/framework.module.html}{OSGi
Alliance's core specification document}.
\section{Declaring a Fragment Host}\label{declaring-a-fragment-host}
There are two players in this game: the fragment and the host. The
fragment is a parasitic module that attaches itself to a host. That
sounds harsh, so let's compare the fragment-host relationship to the
relationship between a pilot fish and a huge, scary shark. It's
symbiotic, really. Your fragment module benefits by not doing much work
(like the pilot fish who benefits from the shark's hunting prowess). In
return, the host module gets whatever benefits you've conjured up in
your fragment's JSPs (for the shark, it gets free dental cleanings!). To
the OSGi runtime, your fragment is part of the host module.
Your fragment must declare two things to the OSGi runtime regarding the
host module:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The Bundle Symbolic Name of the host module. This is the module
containing the original JSP.
\item
The exact version of the host module to which the fragment belongs.
\end{enumerate}
Both are declared using the OSGi manifest header \texttt{Fragment-Host}.
\begin{verbatim}
Fragment-Host: com.liferay.login.web;bundle-version="[1.0.0,1.0.1)"
\end{verbatim}
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, and that's a good thing. A new version of the host module might
have changed its JSPs, so if your now-incompatible version of the JSP is
applied to the host module, you'll break the functionality of the host.
It's better to detach your fragment and leave it lonely in the OSGi
runtime than it is to break the functionality of an entire application.
\section{Provide the Overridden JSP}\label{provide-the-overridden-jsp}
There are two possible naming conventions for targeting the host
original JSP: \texttt{portal} or \texttt{original}. For example, if the
original JSP is in the folder \texttt{/META-INF/resources/login.jsp},
then the fragment bundle should contain a JSP with the same path, using
the following pattern:
\begin{verbatim}
\end{verbatim}
After that, make your modifications. Just make sure you mimic the host
module's folder structure when overriding its JAR. If you're overriding
Liferay's login application's \texttt{login.jsp} for example, you'd put
your own \texttt{login.jsp} in
\begin{verbatim}
my-jsp-fragment/src/main/resources/META-INF/resources/login.jsp
\end{verbatim}
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 \texttt{create\_account.jsp}:
\begin{verbatim}
<%@ include file="/init.jsp" %>
" />
<%
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 %>
\end{verbatim}
\section{Using Fragment Host Internal
Packages}\label{using-fragment-host-internal-packages}
To use an internal (unexported) host package, the fragment must
explicitly exclude the package from its \texttt{Import-Package:}
manifest header. For example, this \texttt{Import-Package} header
excludes packages that match
\texttt{com.liferay.portal.search.web.internal.*}.
\begin{verbatim}
Import-Package: !com.liferay.portal.search.web.internal.*,*
\end{verbatim}
Unless you explicitly exclude the package, bnd adds the package to the
\texttt{Import-Package:} header. Attempting to start the fragment while
requiring an unexported package fails because the package is an
unresolved requirement. For this reason, make sure to exclude such
packages from your fragment's \texttt{Import-Package:} header.
Each fragment has full access to the host packages, including its
internal (unexported) packages already.
Now you can easily modify the JSPs of any application in Liferay.
\includegraphics{./images/sharks.jpg}
\section{Related Topics}\label{related-topics-17}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{JSP
Overrides Using Portlet Filters}
\end{itemize}
\chapter{JSP Overrides Using Custom JSP
Bag}\label{jsp-overrides-using-custom-jsp-bag}
Liferay's API based approaches to overriding JSPs (i.e.,
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes}{Dynamic
Includes} and
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{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.
\noindent\hrulefill
\textbf{Important:} Liferay cannot guarantee that JSPs overridden using
Custom JSP Bag can be upgraded.
\noindent\hrulefill
A Custom JSP Bag module must satisfy these criteria:
\begin{itemize}
\item
Provides and specifies a custom JSP for the JSP you're extending.
\item
Includes a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBag.html}{\texttt{CustomJspBag}}
implementation for serving the custom JSPs.
\end{itemize}
The module provides transportation for this code into Liferay's OSGi
runtime. After you
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{create
your new module}, continue with providing your custom JSP.
\section{Providing a Custom JSP}\label{providing-a-custom-jsp}
Create your JSPs to override Liferay DXP core JSPs. If you're using the
Maven
\href{https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html}{Standard
Directory Layout}, place your JSPs under
\texttt{src/main/resources/META-INF/jsps}. For example, if you're
overriding
\begin{verbatim}
portal-web/docroot/html/common/themes/bottom-ext.jsp
\end{verbatim}
place your custom JSP at
\begin{verbatim}
[your module]/src/main/resources/META-INF/jsps/html/common/themes/bottom-ext.jsp
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} If you place custom JSPs somewhere other than
\texttt{src/main/resources/META-INF/jsps} in your module, assign that
location to a \texttt{-includeresource:\ META-INF/jsps=} directive in
your module's \texttt{bnd.bnd} file. For example, if you place custom
JSPs in a folder \texttt{src/META-INF/custom\_jsps} in your module,
specify this in your \texttt{bnd.bnd}:
\begin{verbatim}
-includeresource: META-INF/jsps=src/META-INF/custom_jsps
\end{verbatim}
\noindent\hrulefill
\section{Implement a Custom JSP Bag}\label{implement-a-custom-jsp-bag}
Liferay DXP (specifically the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBagRegistryUtil.html}{\texttt{CustomJspBagRegistryUtil}
class}) loads JSPs from
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBag.html}{\texttt{CustomJspBag}}
services. Here are steps for implementing a custom JSP bag.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your module, create a class that implements
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBag.html}{\texttt{CustomJspBag}}.
\item
Register your class as an OSGi service by adding an
\texttt{@Component} annotation to it, like this:
\begin{verbatim}
@Component(
immediate = true,
property = {
"context.id=BladeCustomJspBag",
"context.name=Test Custom JSP Bag",
"service.ranking:Integer=100"
}
)
\end{verbatim}
\begin{itemize}
\tightlist
\item
\textbf{\texttt{immediate\ =\ true}:} Makes the service available on
module activation.
\item
\textbf{\texttt{context.id}:} Your custom JSP bag class name.
Replace \texttt{BladeCustomJspBag} with your class name.
\item
\textbf{\texttt{context.name}:} A more human readable name for your
service. Replace it with a name of your own.
\item
\textbf{\texttt{service.ranking:Integer}:} A priority for your
implementation. The container chooses the implementation with the
highest priority.
\end{itemize}
\item
Implement the \texttt{getCustomJspDir} method to return the folder
path in your module's JAR where the JSPs reside (for example,
\texttt{META-INF/jsps}).
\begin{verbatim}
@Override
public String getCustomJspDir() {
return "META-INF/jsps/";
}
\end{verbatim}
\item
Create an \texttt{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.
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
_bundle = bundleContext.getBundle();
_customJsps = new ArrayList<>();
Enumeration entries = _bundle.findEntries(
getCustomJspDir(), "*.jsp", true);
while (entries.hasMoreElements()) {
URL url = entries.nextElement();
_customJsps.add(url.getPath());
}
}
private Bundle _bundle;
private List _customJsps;
\end{verbatim}
\item
Implement the \texttt{getCustomJsps} method to return the list of this
module's custom JSP URL paths.
\begin{verbatim}
@Override
public List getCustomJsps() {
return _customJsps;
}
\end{verbatim}
\item
Implement the \texttt{getURLContainer} method to return a new
\texttt{com.liferay.portal.kernel.url.URLContainer}. Instantiate the
URL container and override its \texttt{getResources} and
\texttt{getResource} methods. The \texttt{getResources} method looks
up all the paths to resources in the container by a given path. It
returns a \texttt{HashSet} of \texttt{Strings} for the matching custom
JSP paths. The \texttt{getResource} method returns one specific
resource by its name (the path included).
\begin{verbatim}
@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 getResources(String path) {
Set paths = new HashSet<>();
for (String entry : _customJsps) {
if (entry.startsWith(path)) {
paths.add(entry);
}
}
return paths;
}
};
\end{verbatim}
\item
Implement the \texttt{isCustomJspGlobal} method to return
\texttt{true}.
\begin{verbatim}
@Override
public boolean isCustomJspGlobal() {
return true;
}
\end{verbatim}
\end{enumerate}
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.
\section{Extend a JSP}\label{extend-a-jsp}
If you want to add something to a core JSP, see if it has an empty
\texttt{-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 \texttt{-ext.jsp}, you're only relying on the original JSP including
the \texttt{-ext.jsp}. For an example, open
\texttt{portal-web/docroot/html/common/themes/bottom.jsp}, and scroll to
the end. You'll see this:
\begin{verbatim}
\end{verbatim}
If you must add something to \texttt{bottom.jsp}, override
\texttt{bottom-ext.jsp}.
Since Liferay DXP 7.0, the content from the following JSP files formerly
in \texttt{html/common/themes} are inlined to improve performance.
\begin{itemize}
\tightlist
\item
\texttt{body\_bottom-ext.jsp}
\item
\texttt{body\_top-ext.jsp}
\item
\texttt{bottom-ext.jsp}
\item
\texttt{bottom-test.jsp}
\end{itemize}
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 API based approaches to overriding JSPs
mentioned at the beginning of the article.
\section{Site Scoped JSP
Customization}\label{site-scoped-jsp-customization}
In Liferay Portal 6.2, you could use
\href{/docs/6-2/tutorials/-/knowledge_base/t/customizing-sites-and-site-templates-with-application-adapters}{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
\href{/docs/6-2/tutorials/-/knowledge_base/t/customizing-sites-and-site-templates-with-application-adapters}{as
you would have for Liferay Portal 6.2}, and deploy it to 7.0. It will
still work. However, note that this approach is deprecated in 7.0 and
won't be supported at all in Liferay 8.0.
\section{Related Topics}\label{related-topics-18}
\begin{itemize}
\tightlist
\item
\href{/docs/7-1/tutorials/-/knowledge_base/t/upgrading-core-jsp-hooks}{Upgrading
Core JSP Hooks}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{JSP
Overrides Using Portlet Filters}
\end{itemize}
\chapter{Overriding Inline Content Using
JSPs}\label{overriding-inline-content-using-jsps}
Some Liferay DXP core content, such as tag library tags, can only be
overridden using JSPs ending in \texttt{.readme}. The suffix
\texttt{.readme} facilitates finding them. The code from these JSPs is
now inlined (brought into Liferay DXP Java source files) to improve
performance. Liferay DXP ignores JSP files with the \texttt{.readme}
suffix. If you add code to a JSP \texttt{.readme} file and remove the
\texttt{.readme} suffix, Liferay DXP uses that JSP instead of the core
inline content. This tutorial shows you how to make these
customizations.
\noindent\hrulefill
\textbf{Important:} This type of customization is a last resort. Your
override may break due to the nature of this implementation, and core
functionality can go down with it. Liferay cannot guarantee that content
overridden using JSP \texttt{.readme} files can be upgraded.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Warning:} Modifying a Liferay DXP tag library tag affects all
uses of that tag in your Liferay DXP installation.
\noindent\hrulefill
Here's how to override inline content using JSPs:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Create a
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-custom-jsp-bag}{Custom
JSP Bag} for deploying your JSP. Note the module folder you're storing
the JSPs in: the default folder is
\texttt{{[}your\ module{]}/src/main/resources/META-INF/jsps/}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** you can develop your JSP anywhere, but a Custom JSP Bag module
provides a straightforward way to build and deploy it.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Download the Liferay DXP source code or browse the source code on
\href{https://github.com/liferay/liferay-portal/tree/7.2.x}{GitHub
(Liferay Portal CE)}.
\item
Search the source code for a \texttt{.jsp.readme} file that overrides
the tag you're customizing.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Files ending in `-ext.jsp.readme` let you prepend or
append new content to existing content. Examples include the
`bottom-test.jsp.readme`, `bottom-ext.jsp.readme`,
`body_top-ext.jsp.readme`, and `body_bottom-ext.jsp.readme` files in
the Liferay DXP application's `portal-web/docroot/html/common/themes` folder.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\item
Copy the \texttt{.jsp.readme} file into your project and drop the
\texttt{.readme} suffix. Use the same relative file path Liferay DXP
uses for the \texttt{.jsp.readme} file. For example, if the file in
Liferay DXP is
\begin{verbatim}
portal-web/docroot/html/taglib/aui/fieldset/start.jsp.readme
\end{verbatim}
use file path
\begin{verbatim}
[your module]/src/main/resources/META-INF/jsps/html/taglib/aui/fieldset/start.jsp
\end{verbatim}
\item
Familiarize yourself with the current UI content and logic, so you can
override it appropriately. Tag library tag content logic, for example,
is in the respective \texttt{*Tag.java} file under
\texttt{util-taglib/src/com/liferay/taglib/{[}tag\ library{]}/}.
\item
Develop your new logic, keeping in mind the current inline logic
you're replacing.
\item
Deploy your JSP.
\end{enumerate}
Liferay DXP uses your JSP in place of the current inline logic. If you
want to walk through an example override, continue with this tutorial.
Otherwise, congratulations on a modified \texttt{.jsp.readme} file to
override core inline content!
\section{Example: Overriding the fieldset Taglib
Tag}\label{example-overriding-the-fieldset-taglib-tag}
This example demonstrates changing the \texttt{liferay:aui} tag
library's \texttt{fieldset} tag. Browsing the Liferay DXP web
application or the source code at
\texttt{portal-web/docroot/html/taglib/aui/fieldset} reveals these
files:
\begin{itemize}
\tightlist
\item
\texttt{start.jsp.readme}
\item
\texttt{end.jsp.readme}
\end{itemize}
They can override the logic that creates the start and end of the
\texttt{fieldset} tag. The \texttt{FieldsetTag.java} class's
\texttt{processStart} and \texttt{processEnd} methods implement the
current inline content. Here's the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/util-taglib/src/com/liferay/taglib/aui/FieldsetTag.java\#L86-L141}{\texttt{processStart}}
method:
\begin{verbatim}
@Override
protected int processStartTag() throws Exception {
JspWriter jspWriter = pageContext.getOut();
jspWriter.write("");
MessageTag messageTag = new MessageTag();
messageTag.setKey(lable);
messageTag.setLocalizeKey(getLocalizeLabel());
messageTag.doTag(pageContext);
String helpMessage = getHelpMessage();
if (helpMessage != null) {
IconHelpTag iconHelpTag = new IconHelpTag();
iconHelpTag.setMessage(helpMessage);
iconHelpTag.doTag(pageContext);
}
jspWriter.write(" ");
}
if (getColumn()) {
jspWriter.write("");
}
else {
jspWriter.write("
");
}
return EVAL_BODY_INCLUDE;
}
\end{verbatim}
The code above does this:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Write
\texttt{\textless{}fieldset\ class=\textbackslash{}"fieldset}starting
tag.
\item
Write the CSS class name attribute.
\item
If the tag has an ID, add the \texttt{id} as an attribute.
\item
Write the tag's dynamic attribute (map).
\item
Close the starting \texttt{fieldset} tag.
\item
Get the tag's \texttt{label} attribute.
\item
Write the starting \texttt{legend} element.
\item
Use \texttt{getLocalizeLabel()} to add the localized label in the
\texttt{legend}.
\item
If there's a help message (retrieved from \texttt{getHelpMessage()}),
write it in an \texttt{icon-help-tag}.
\item
Write the closing \texttt{legend} tag.
\item
If there's a column attribute, write
\texttt{\textless{}div\ class=\textbackslash{}"row\textbackslash{}"\textgreater{}};
else write
\texttt{\textless{}div\ class=\textbackslash{}"\textbackslash{}"\textgreater{}}.
\end{enumerate}
Replicating the current logic in your custom JSP helps you set up the
tag properly for customizing. The \texttt{init.jsp} for
\texttt{fieldset} initializes all the variables required to create the
starting tag. You can use the variables in the \texttt{start.jsp}. The
logic from \texttt{FieldsetTag}'s \texttt{processStart} method converted
to JSP code for \texttt{start.jsp} (renamed from
\texttt{start.jsp.readme}) would look like this:
\begin{verbatim}
<%@ include file="/html/taglib/aui/fieldset/init.jsp" %>
<%= InlineUtil.buildDynamicAttributes(dynamicAttributes) %>>
">
\end{verbatim}
\noindent\hrulefill
\textbf{Tip:} A \texttt{*Tag.java} file's history might reveal original
JSP code that was inlined. For example, the logic from \texttt{fieldset}
tag's
\href{https://github.com/liferay/liferay-portal/blob/df22ba66eff49b76404cfda908d3cd024efbebd9/portal-web/docroot/html/taglib/aui/fieldset/start.jsp}{\texttt{start.jsp}}
was inlined in
\href{https://github.com/liferay/liferay-portal/commit/7fba0775bcc1d1a0bc4d107cabfb41a90f15937c\#diff-2ad802b4c0d8f7a2da45b895e89d6e46}{this
commit}.
\noindent\hrulefill
On deploying the \texttt{start.jsp}, the \texttt{fieldset} tags render
the same as they did before. This is expected because it uses the same
logic as \texttt{FieldsetTag}'s \texttt{processStart} method.
\begin{figure}
\centering
\includegraphics{./images/jsp-readme-inline-fieldset.png}
\caption{Liferay DXP's home page's search and sign in components are in
a \texttt{fieldset}.}
\end{figure}
The \texttt{fieldset} starting logic is ready for customization. To test
that this works, you'll print the word \emph{test} surrounded by
asterisks before the end of the \texttt{fieldset} tag's starting logic.
Insert this line before the \texttt{start.jsp}'s last \texttt{div} tag:
\begin{verbatim}
\end{verbatim}
Redeploy the JSP and refresh the page to see the text printed above the
\texttt{fieldset}'s fields.
\begin{figure}
\centering
\includegraphics{./images/jsp-readme-override-inline-fieldset.png}
\caption{Before the \texttt{fieldset}'s nested fields, it prints
\emph{test} surrounded by asterisks.}
\end{figure}
You know how to override specific Liferay DXP core inline content using
Liferay's \texttt{.jsp.readme} files.
\section{Related Topics}\label{related-topics-19}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes}{Customizing
JSPs with Dynamic Includes}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{JSP
Overrides Using Portlet Filters}
\end{itemize}
\chapter{Customizing Widgets}\label{customizing-widgets}
It would be nice to apply display changes to specific widget instances
without having to create a hook (e.g., HTML-related change) or change a
theme (e.g., CSS-related change). Ideally, you should be able to enable
authorized users to apply custom display interfaces to widgets.
Be of good cheer! That's precisely what
\href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Widget
Templates} provide. Now you can customize the way widgets appear on a
page, removing limitations to the way content is displayed. With Widget
Templates, you can define display templates to render asset-centric
widgets. Some default widgets already have templating capabilities
(e.g., \emph{Web Content} and \emph{Dynamic Data Lists}), in which you
can add as many display options (or templates) as you want. You can also
add them to your own applications.
Some portlets that already support Widget Templates are
\begin{itemize}
\tightlist
\item
\emph{Asset Publisher}
\item
\emph{Blogs}
\item
\emph{Breadcrumb}
\item
\emph{Categories Navigation}
\item
\emph{Language Selector}
\item
\emph{Media Gallery}
\item
\emph{Navigation Menu}
\item
\emph{RSS Publisher}
\item
\emph{Site Map}
\item
\emph{Tags Navigation}
\item
\emph{Wiki}
\end{itemize}
To leverage the Widget Template API, follow these steps:
\begin{itemize}
\tightlist
\item
register your portlet to use Widget Templates
\item
define your display template definitions
\item
define permissions
\item
expose the Widget Template functionality to users
\end{itemize}
The detailed steps are in the
\href{/docs/7-2/customization/-/knowledge_base/c/implementing-widget-templates}{Implementing
Widget Templates} article. Here's a high level overview of what you'll
do.
\section{Implementing the TemplateHandler
Interface}\label{implementing-the-templatehandler-interface}
To register your portlet to use Widget Templates, you must implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/template/TemplateHandler.html}{\texttt{TemplateHandler}}
interface. Read the interface's Javadoc for more information on each
method provided by the interface.
Each of the methods in this class have a significant role in defining
and implementing Widget Templates for your custom portlet. The list
below highlights some of the methods defined specifically for Widget
Templates:
\texttt{getClassName()}: Defines the type of entry your portlet is
rendering.
\texttt{getName()}: Declares the name of your Widget Template type
(typically, the name of the portlet).
\texttt{getResourceName()}: Specifies which resource is using the Widget
Template (e.g., a portlet) for permission checking. This method must
return the portlet's fully qualified portlet ID (e.g.,
\texttt{com\_liferay\_wiki\_web\_portlet\_WikiPortlet}).
\texttt{getTemplateVariableGroups()}: Defines the variables exposed in
the template editor.
\texttt{getTemplatesConfigPath()}: Defines the configuration file
containing the display template definition.
Next, you must define your display template definition(s).
\section{Defining Display Template
Definitions}\label{defining-display-template-definitions}
Once you've registered your portlet to use Widget Templates, you should
create the display template definitions. These are used to style the
content displayed in the widget.
You must create a \texttt{portlet-display-templates.xml} configuration
file to define the definitions and point to their styled templated
(e.g., FreeMarker). Then you must create the templates. These template
definitions are available to apply from a widget's Configuration menu.
Next, you define permissions for your portlet's Widget Templates.
\section{Defining Permissions}\label{defining-permissions}
You must define permissions for your Widget Templates; without
permissions, anyone in the Site could access and change your widget's
display templates. Configuring permissions lets administrative users
grant permissions only to the Roles that should create and manage
display templates.
This is done by creating a \texttt{default.xml} file in your portlet
defining the permissions you want to enforce, wiring it up with your
portlet, and configuring them for use in Liferay DXP. You can visit
\href{/docs/7-2/customization/-/knowledge_base/c/implementing-widget-templates}{this
article} for step-by-step instructions on how to complete this.
Next, you'll learn how to expose Widget Template selection for users.
\section{Exposing the Widget Template
Selection}\label{exposing-the-widget-template-selection}
To expose the Widget Template option to your users, use the
\texttt{\textless{}liferay-ui:ddm-template-selector\textgreater{}} tag
in the JSP file that controls your portlet's configuration. This tag
requires the following parameters:
\texttt{className}: your entity's class name.
\texttt{contextObjects}: accepts a
\texttt{Map\textless{}String,\ Object\textgreater{}} with any object you
want to the template context.
\texttt{displayStyle}: your portlet's display style.
\texttt{displayStyleGroupId}: your portlet's display style group ID.
\texttt{entries}: accepts a list of your entities (e.g.,
\texttt{List\textless{}YourEntity\textgreater{}}).
The variables \texttt{displayStyle} and \texttt{displayStyleGroupId} are
preferences that your portlet stores when you use this taglib and your
portlet uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BaseJSPSettingsConfigurationAction.html}{\texttt{BaseJSPSettingsConfigurationAction}}
or
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/DefaultConfigurationAction.html}{\texttt{DefaultConfigurationAction}}.
Otherwise, you must obtain the value of those parameters and store them
manually in your configuration class.
\section{Recommendations for Using Widget
Templates}\label{recommendations-for-using-widget-templates}
You can harness a lot of power by leveraging the Widget Template API. Be
careful, for with great power, comes great responsibility! Here are some
practices you can use to optimize your portlet's performance and
security.
First let's talk about security. You may want to hide some classes or
packages from the template context to limit the operations that Widget
Templates can perform. Liferay DXP provides some system settings, which
can be accessed by navigating to \emph{Control Panel} →
\emph{Configuration} → \emph{System Settings} → \emph{Template Engines}
→ \emph{FreeMarker Engine}, to define the restricted classes, packages,
and variables. In particular, you may want to add
\texttt{serviceLocator} to the list of default values assigned to the
FreeMarker Engine Restricted variables.
Widget Templates introduce additional processing tasks when your portlet
is rendered. To minimize negative effects on performance, make your
templates as minimal as possible by focusing on their presentation,
while using the existing API for complex operations. The best way to
make Widget Templates efficient is to know your template context well,
and understand what you can use from it. Fortunately, you don't need to
memorize the context information, thanks to Liferay DXP's advanced
template editor!
To navigate to the template editor for Widget Templates, go to the Site
Admin menu and select \emph{Configuration} → \emph{Widget Templates} and
then click \emph{Add} and select the specific portlet on which you
decide to create a custom template.
The template editor provides fields, general variables, and utility
variables customized for the portlet you chose. These variable
references are on the left-side panel of the template editor. Place your
cursor where you want the variable placed and click the desired variable
to insert it. You can learn more about the template editor in
\href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Styling
Widgets with Widget Templates}.
Finally, don't forget to run performance tests and tune the template
cache options by modifying the \emph{Resource modification check} field
in \emph{System Settings} → \emph{Template Engines} → \emph{FreeMarker
Engine}.
Widget Templates provide power to your portlets by providing infinite
ways of editing your portlet to create new interfaces for your users. Be
sure to configure your FreeMarker templates appropriately for the most
efficient customization process.
Continue on to add support for Widget Templates in your portlet.
\chapter{Implementing Widget
Templates}\label{implementing-widget-templates}
\href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Widget
Templates} are ways to customize how a widget looks. You can create
templates for a widget's display and then choose which template is
active.
\begin{figure}
\centering
\includegraphics{./images/widget-template-dropdown.png}
\caption{By using a custom display template, your portlet's display can
be customized.}
\end{figure}
To add Widget Template support to your portlet, follow the steps below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create and register a custom \texttt{*PortletDisplayTemplateHandler}
component. Liferay provides the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portletdisplaytemplate/BasePortletDisplayTemplateHandler.html}{\texttt{BasePortletDisplayTemplateHandler}}
as a base implementation for you to extend. You can check the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/template/TemplateHandler.html}{\texttt{TemplateHandler}}
interface Javadoc to learn about each template handler method.
The \texttt{@Component} annotation ties your handler to a specific
portlet by setting the property \texttt{javax.portlet.name} to your
portlet's name. The same property should be found in your portlet
class. For example,
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name="+ AssetCategoriesNavigationPortletKeys.ASSET_CATEGORIES_NAVIGATION
},
service = TemplateHandler.class
)
\end{verbatim}
The Site Map widget sets the \texttt{@Component} annotation like this:
\begin{verbatim}
@Component(
immediate = true,
property = "javax.portlet.name=" + SiteNavigationSiteMapPortletKeys.SITE_NAVIGATION_SITE_MAP,
service = TemplateHandler.class
)
public class SiteNavigationSiteMapPortletDisplayTemplateHandler
extends BasePortletDisplayTemplateHandler {
}
\end{verbatim}
You'll continue stepping through the Site map widget's
\texttt{TemplateHandler} implementation next.
\item
Override the base class' \texttt{getClassName()},
\texttt{getName(...)}, and \texttt{getResourceName()} methods:
\begin{verbatim}
@Override
public String getClassName() {
return LayoutSet.class.getName();
}
@Override
public String getName(Locale locale) {
String portletTitle = _portal.getPortletTitle(
SiteNavigationSiteMapPortletKeys.SITE_NAVIGATION_SITE_MAP,
ResourceBundleUtil.getBundle(locale, getClass()));
return LanguageUtil.format(locale, "x-template", portletTitle, false);
}
@Override
public String getResourceName() {
return SiteNavigationSiteMapPortletKeys.SITE_NAVIGATION_SITE_MAP;
}
\end{verbatim}
These methods return the template handler's class name, the template
handler's name (via
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{resource
bundle}), and the resource name associated with the Widget Template,
respectively.
\item
Override the \texttt{getTemplateVariableGroups(...)} method to return
your widget template's script variable groups. These are used to
display hints in the template editor palette.
\begin{verbatim}
@Override
public Map
getTemplateVariableGroups(
long classPK, String language, Locale locale)
throws Exception {
Map templateVariableGroups =
super.getTemplateVariableGroups(classPK, language, locale);
TemplateVariableGroup templateVariableGroup =
templateVariableGroups.get("fields");
templateVariableGroup.empty();
templateVariableGroup.addCollectionVariable(
"pages", List.class, PortletDisplayTemplateConstants.ENTRIES,
"page", Layout.class, "curPage", "getName(locale)");
templateVariableGroup.addVariable(
"site-map-display-context",
SiteNavigationSiteMapDisplayContext.class, "siteMapDisplayContext");
return templateVariableGroups;
}
\end{verbatim}
For this example, the \emph{Pages} and \emph{Site Map Display Context}
fields are added to the default variables in the template editor
palette.
\begin{figure}
\centering
\includegraphics{./images/widget-template-fields.png}
\caption{You can click a variable to add it to the template editor.}
\end{figure}
\item
Set your display template configuration file path:
\begin{verbatim}
@Override
protected String getTemplatesConfigPath() {
return "com/liferay/site/navigation/site/map/web/portlet/template" +
"/dependencies/portlet-display-templates.xml";
}
\end{verbatim}
This method returns the XML file containing the display template
definitions available for your portlet. You'll create this file next.
\item
Create your \texttt{portlet-display-templates.xml} file to define your
display template definitions. For example,
\begin{verbatim}
site-map-multi-column-layout-ftl
portlet-display-template-name-multi-column-layout
portlet-display-template-description-multi-column-layout-sitemap
ftl
com/liferay/site/navigation/site/map/web/portlet/template/dependencies/portlet_display_template_multi_column_layout.ftl
false
\end{verbatim}
This defined template option is read and presented to the user through
the widget's Configuration menu. Navigate to the Site Map widget's
Configuration menu and you can confirm the \emph{Multi Column Layout}
option is available.
\begin{figure}
\centering
\includegraphics{./images/widget-config-display.png}
\caption{You can choose the Widget Template you want to apply from the
widget's Configuration menu.}
\end{figure}
This template is created using FreeMarker. You'll create this template
option next.
\item
Create your template script file that you specified in the previous
step. For the Site Map widget, its Multi Column Layout option is
configured in a FreeMarker template like this:
\begin{verbatim}
<#if entries?has_content>
<@liferay_aui.row>
<#list entries as entry>
<#if layoutPermission.containsWithoutViewableGroup(permissionChecker, entry, "VIEW")>
<@liferay_aui.col width=25>
<@displayPages
depth=1
pages=entry.getChildren(permissionChecker)
/>
@liferay_aui.col>
#if>
#list>
@liferay_aui.row>
#if>
<#macro displayPages
depth
pages
>
<#if pages?has_content && ((depth < displayDepth?number) || (displayDepth?number == 0))>
<#list pages as page>
<#if pageType.isBrowsable()>
href="${portalUtil.getLayoutURL(page, themeDisplay)}"
#if>
>${page.getName(locale)}
<@displayPages
depth=depth + 1
pages=page.getChildren(permissionChecker)
/>
#list>
#if>
#macro>
\end{verbatim}
This template definition enforces page permissions, formats how the
pages are displayed (multi column), and provides clickable links for
each page.
\item
Your widget must define permissions for creating and managing display
templates. Add the action key \texttt{ADD\_PORTLET\_DISPLAY\_TEMPLATE}
to your portlet's
\texttt{/src/main/resources/resource-actions/default.xml} file:
\begin{verbatim}
...
yourportlet
ADD_PORTLET_DISPLAY_TEMPLATE
ADD_TO_PAGE
CONFIGURATION
VIEW
...
...
\end{verbatim}
\item
If your widget hasn't defined Liferay permissions before, create a
file named \texttt{portlet.properties} in the \texttt{/resources}
folder and add the following contents providing the path to your
\texttt{default.xml}:
\begin{verbatim}
include-and-override=portlet-ext.properties
resource.actions.configs=resource-actions/default.xml
\end{verbatim}
\item
Now expose the Widget Template selector to your users. Include the
\texttt{\textless{}liferay-ddm:template-selector\textgreater{}} tag in
the JSP file you're using to control your portlet's configuration.
For example, it may be helpful for you to insert a
\texttt{\textless{}liferay-frontend:fieldset\textgreater{}} in your
configuration JSP file like this:
\begin{verbatim}
\end{verbatim}
In this JSP, the
\texttt{\textless{}liferay-ddm:template-selector\textgreater{}} tag
specifies the Display Template drop-down menu to be used in the
widget's Configuration menu.
\item
You must now extend your view code to render your portlet using the
selected Widget Template.
First, initialize the Java variables needed for the Widget Template:
\begin{verbatim}
<%
String displayStyle = GetterUtil.getString(portletPreferences.getValue("displayStyle", StringPool.BLANK));
long displayStyleGroupId = GetterUtil.getLong(portletPreferences.getValue("displayStyleGroupId", null), scopeGroupId);
%>
\end{verbatim}
Next, you can test if the Widget Template is configured, grab the
entities to be rendered, and render them using the Widget Template.
The tag
\texttt{\textless{}liferay-ddm:template-renderer\textgreater{}} aids
with this process. It automatically uses the selected template or
renders its body if no template is selected.
Here's some example code that demonstrates implementing this:
\begin{verbatim}
<%-- The code that renders the default view should be inserted here. --%>
\end{verbatim}
In this step, you initialized variables dealing with the display
settings (\texttt{displayStyle} and \texttt{displayStyleGroupId}) and
passed them to the tag along with other parameters.
As an example, the Site Map widget implements the
\texttt{\textless{}liferay-ddm:template-renderer\textgreater{}} tag in
its \texttt{view.jsp} like this:
\begin{verbatim}
<%= siteNavigationSiteMapDisplayContext.buildSiteMap() %>
\end{verbatim}
This logic builds the site's navigation map when the widget is added
to a page.
\end{enumerate}
Awesome! Your portlet now supports Widget Templates! Once your script is
uploaded and saved, Users with the specified Roles can select the
template when they're configuring the display settings of your portlet
on a page. You can visit the
\href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Styling
Widgets with Widget Templates} section for more details on using Widget
Templates.
\chapter{Dynamic Includes}\label{dynamic-includes}
Dynamic includes expose extension points in JSPs for injecting
additional HTML, adding resources, modifying editors, and more. Several
dynamic includes are available. Once you know the dynamic include's key,
you can use it to
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes}{create
a module to inject your content}.
This section of tutorials lists the available dynamic include keys,
along with a description of their use cases and a code example.
The following extension points are covered in this section:
\begin{longtable}[]{@{}
>{\centering\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.4074}}
>{\centering\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5926}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\centering
Extension Point
\end{minipage} & \begin{minipage}[b]{\linewidth}\centering
Purpose
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\href{/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes}{bottom}
& Load additional HTML or scripts in the bottom of the theme's body \\
\href{/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes}{top\_head}
& Load additional links in the theme's head \\
\href{/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include}{top\_js}
& Load additional JS files in the theme's head \\
\href{/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes}{WYSIWYG}
& Add resources to the editor, listen to events, update the
configuration, etc. \\
\end{longtable}
\chapter{WYSIWYG Editor Dynamic
Includes}\label{wysiwyg-editor-dynamic-includes}
All WYSIWYG editors share the same dynamic include extension points for
these things:
\begin{itemize}
\item
Adding resources, plugins, etc. to the editor:
com.liferay.frontend.editor.\texttt{editorType}.web\#\texttt{editorName}\#additionalResources
\item
Accessing the editor instance to listen to events, configure it, etc:
com.liferay.frontend.editor.\texttt{editorType}.web\#\texttt{editorName}\#onEditorCreate
\end{itemize}
The table below shows the \texttt{editorType}, variable, and
\texttt{editorName}s for each editor:
\begin{longtable}[]{@{}ccc@{}}
\toprule\noalign{}
editorType & variable & editorName \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
alloyeditor & alloyEditor & alloyeditor \\
~ & ~ & alloyeditor\_bbcode \\
~ & ~ & alloyeditor\_creole \\
ckeditor & ckEditor & ckeditor \\
~ & ~ & ckeditor\_bbcode \\
~ & ~ & ckeditor\_creole \\
tinymce & tinyMCEEditor & tinymce \\
~ & ~ & tinymce\_simple \\
\end{longtable}
The example below alerts the user when he/she pastes content into the
CKEditor.
\texttt{*DynamicInclude} Java Class:
\begin{verbatim}
@Component(immediate = true, service = DynamicInclude.class)
public class CKEditorOnEditorCreateDynamicInclude implements DynamicInclude {
@Override
public void include(
HttpServletRequest request, HttpServletResponse response,
String key)
throws IOException {
Bundle bundle = _bundleContext.getBundle();
URL entryURL = bundle.getEntry(
"/META-INF/resources/ckeditor/extension/ckeditor_alert.js");
StreamUtil.transfer(
entryURL.openStream(), response.getOutputStream(), false);
}
@Override
public void register(
DynamicInclude.DynamicIncludeRegistry dynamicIncludeRegistry) {
dynamicIncludeRegistry.register(
"com.liferay.frontend.editor.ckeditor.web#ckeditor#onEditorCreate");
}
@Activate
protected void activate(BundleContext bundleContext) {
_bundleContext = bundleContext;
}
private BundleContext _bundleContext;
}
\end{verbatim}
Example JavaScript:
\begin{verbatim}
// ckEditor variable is already available in the execution context
ckEditor.on(
'paste',
function(event) {
event.stop();
alert('Please, do not paste code here!');
}
);
\end{verbatim}
Now you know how to use the WYSIWYG editor dynamic includes.
\section{Related Topics}\label{related-topics-20}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes}{Bottom
JSP Dynamic Includes}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes}{Top
Head JSP Dynamic Includes}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include}{Top
JS Dynamic Include}
\end{itemize}
\chapter{Top Head JSP Dynamic
Includes}\label{top-head-jsp-dynamic-includes}
The \texttt{top\_head.jsp} dynamic includes load additional links in the
theme's head. It uses the following keys:
Load additional links in the theme's head before the existing ones:
\begin{verbatim}
/html/common/themes/top_head.jsp#pre
\end{verbatim}
Alternatively, you can load additional links in the theme's head, after
the existing ones:
\begin{verbatim}
/html/common/themes/top_head.jsp#post
\end{verbatim}
The example below injects a link into the top of the
\texttt{top\_head.jsp}:
\begin{verbatim}
@Component(immediate = true, service = DynamicInclude.class)
public class CssTopHeadDynamicInclude extends BaseDynamicInclude {
@Override
public void include(
HttpServletRequest request, HttpServletResponse response,
String key)
throws IOException {
PrintWriter printWriter = response.getWriter();
String content =
" ";
printWriter.println(content);
}
@Override
public void register(DynamicIncludeRegistry dynamicIncludeRegistry) {
dynamicIncludeRegistry.register("/html/common/themes/top_head.jsp#pre");
}
}
\end{verbatim}
Page Source:
\begin{verbatim}
...
...
\end{verbatim}
Note that the link's \texttt{href} attribute's value
\texttt{/o/my-custom-dynamic-include/} is provided by the OSGi module's
\texttt{Web-ContextPath} (\texttt{/my-custom-dynamic-include} in the
example).
Now you know how to use the \texttt{top\_head.jsp} dynamic includes.
\section{Related Topics}\label{related-topics-21}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes}{Bottom
JSP Dynamic Includes}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include}{Top
JS Dynamic Include}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes}{WYSIWYG
Editor Dynamic Includes}
\end{itemize}
\chapter{Top JS Dynamic Include}\label{top-js-dynamic-include}
The \texttt{top\_js.jspf} dynamic include adds additional JavaScript
files to the theme's head. For example, you can use this extension point
to include a JS library that you need present in the theme's head:
\begin{verbatim}
/html/common/themes/top_js.jspf#resources
\end{verbatim}
The example below injects a JavaScript file into the top of the
\texttt{top\_js.jspf}:
\texttt{*DynamicInclude} Java Class:
\begin{verbatim}
@Component(immediate = true, service = DynamicInclude.class)
public class JSTopHeadDynamicInclude extends BaseDynamicInclude {
@Override
public void include(
HttpServletRequest request, HttpServletResponse response,
String key)
throws IOException {
PrintWriter printWriter = response.getWriter();
String content = "";
printWriter.println(content);
}
@Override
public void register(
DynamicInclude.DynamicIncludeRegistry dynamicIncludeRegistry) {
dynamicIncludeRegistry.register(
"/html/common/themes/top_js.jspf#resources"
);
}
}
\end{verbatim}
Page Source:
\begin{verbatim}
...
...
\end{verbatim}
Note that the JavaScript \texttt{src} attribute's value
\texttt{/o/my-custom-dynamic-include/...} is provided by the OSGi
module's \texttt{Web-ContextPath} (\texttt{/my-custom-dynamic-include}
in the example).
Now you know how to use the \texttt{top\_js.jspf} dynamic include.
\section{Related Topics}\label{related-topics-22}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes}{Bottom
JSP Dynamic Includes}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes}{Top
Head JSP Dynamic Includes}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes}{WYSIWYG
Editor Dynamic Includes}
\end{itemize}
\chapter{Bottom JSP Dynamic Includes}\label{bottom-jsp-dynamic-includes}
The \texttt{bottom.jsp} dynamic includes load additional HTML or scripts
in the bottom of the theme's body. The following keys are available:
Load additional HTML or scripts in the bottom of the theme's body,
before the existing ones:
\begin{verbatim}
/html/common/themes/bottom.jsp#pre
\end{verbatim}
Alternatively, load HTML or scripts in the bottom of the theme's body,
after the existing ones:
\begin{verbatim}
/html/common/themes/bottom.jsp#post
\end{verbatim}
The example below includes an additional script for the Simulation panel
in the bottom of the theme's body, after the existing ones.
\texttt{SimulationDeviceDynamicInclude} Java class:
\begin{verbatim}
@Component(immediate = true, service = DynamicInclude.class)
public class SimulationDeviceDynamicInclude extends BaseDynamicInclude {
@Override
public void include(
HttpServletRequest request, HttpServletResponse response,
String key)
throws IOException {
PrintWriter printWriter = response.getWriter();
printWriter.print(_TMPL_CONTENT);
}
@Override
public void register(DynamicIncludeRegistry dynamicIncludeRegistry) {
dynamicIncludeRegistry.register("/html/common/themes/bottom.jsp#post");
}
private static final String _TMPL_CONTENT = StringUtil.read(
SimulationDeviceDynamicInclude.class,
"/META-INF/resources/simulation_device_dynamic_include.tmpl");
}
\end{verbatim}
\texttt{simulation\_device\_dynamic\_include.tmpl}:
\begin{verbatim}
\end{verbatim}
When the Simulation panel is open, the script adds the
\texttt{lfr-has-simulation-panel} class to the theme's body.
Page Source:
\begin{verbatim}
\end{verbatim}
Now you know how to use the \texttt{bottom.jsp} dynamic includes.
\section{Related Topics}\label{related-topics-23}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes}{Top
Head JSP Dynamic Includes}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include}{Top
JS Dynamic Include}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes}{WYSIWYG
Editor Dynamic Includes}
\end{itemize}
\chapter{Waiting on Lifecycle Events}\label{waiting-on-lifecycle-events}
Liferay registers lifecycle events like portal and database
initialization into the OSGi service registry. Your OSGi Component or
non-component class can listen for these events by way of their service
registrations. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/module/framework/ModuleServiceLifecycle.html}{\texttt{ModuleServiceLifecycle}
interface} defines these names for the lifecycle event services:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/constant-values.html\#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.DATABASE_INITIALIZED}{DATABASE\_INITIALIZED}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/constant-values.html\#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.PORTAL_INITIALIZED}{PORTAL\_INITIALIZED}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/constant-values.html\#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.SPRING_INITIALIZED}{SPRING\_INITIALIZED}
\end{itemize}
Here you'll learn how to wait on lifecycle event services to act on them
from within a component or non-component class.
\section{Taking action from a
component}\label{taking-action-from-a-component}
\href{https://osgi.org/specification/osgi.cmpn/7.0.0/service.component.html}{Declarative
Services (DS)} facilitates waiting for OSGi services and acting on them
once they're available.
Here's a component whose \texttt{doSomething} method is invoked once the
\texttt{ModuleServiceLifecycle.PORTAL\_INITIALIZED} lifecycle event
service and other services are available.
\begin{verbatim}
@Component
public class MyXyz implements XyzApi {
// Plain old OSGi service
@Reference
private SomeOsgiService _someOsgiService;
// Service Builder generated service
@Reference
private DDMStructureLocalService _ddmStructureLocalService;
// Liferay lifecycle service
@Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED)
private ModuleServiceLifecycle _portalInitialized;
@Activate
public void doSomething() {
// `@Activate` method is only executed once all of
// `_someOsgiService`,
// `_ddmStructureLocalService` and
// `_portalInitialized`
// are set.
}
}
\end{verbatim}
Here's how to act on services in your component:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
For each lifecycle event service and OSGi service your component uses,
add a field of that service type and add an \texttt{@Reference}
annotation to that field. The OSGi framework binds the services to
your fields. This field, for example, binds to a standard OSGi
service.
\begin{verbatim}
@Reference
SomeOsgiService _someOsgiService;
\end{verbatim}
\item
To bind to a particular lifecycle event service, target its name as
the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/module/framework/ModuleServiceLifecycle.html}{\texttt{ModuleServiceLifecycle}
interface} defines. This field, for example, targets database
initialization.
\begin{verbatim}
@Reference(target = ModuleServiceLifecycle.DATABASE_INITIALIZED)
ModuleServiceLifecycle _dataInitialized;
\end{verbatim}
\item
Create a method that's triggered on the event(s) and add the
\texttt{@Activate} annotation to that method. It's invoked when all
the service objects are bound to the component's fields.
\end{enumerate}
Your component fires (via its \texttt{@Activate} method) after all its
service dependencies resolve. DS components are the easiest way to act
on lifecycle event services.
\section{Taking action from a non-component
class}\label{taking-action-from-a-non-component-class}
Classes that aren't DS components can use a
\texttt{org.osgi.util.tracker.ServiceTracker} or
\texttt{org.osgi.util.tracker.ServiceTrackerCustomizer} as a
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker\#creating-a-service-tracker-that-tracks-service-events-using-a-callback-handler}{service
callback handler} for the lifecycle event. If you depend on multiple
services, add logic to your \texttt{ServiceTracker} or
\texttt{ServiceTrackerCustomizer} to coordinate taking action when all
the services are available.
To target a lifecycle event service, create a service tracker that
filters on that service. Use \texttt{org.osgi.framework.FrameworkUtil}
to create an \texttt{org.osgi.framework.Filter} that specifies the
service. Then pass that filter as a parameter to the service tracker
constructor. For example, this service tracker filters on the lifecycle
service \texttt{ModuleServiceLifecycle.PORTAL\_INITIALIZED}.
\begin{verbatim}
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
Filter filter = FrameworkUtil.createFilter(
String.format(
"(&(objectClass=%s)%s)",
ModuleServiceLifecycle.class.getName(),
ModuleServiceLifecycle.PORTAL_INITIALIZED));
new ServiceTracker<>(bundleContext, filter, null);
\end{verbatim}
Acting on lifecycle event services in this way requires service callback
handling and some boilerplate code. Using DS components is easier and
more elegant, but at least service trackers provide a way to work with
lifecycle events outside of DS components.
\section{Related Topics}\label{related-topics-24}
\href{/docs/7-1/tutorials/-/knowledge_base/t/service-trackers}{Service
Trackers}
\href{/docs/7-1/reference/-/knowledge_base/r/liferay-startup-phases}{Liferay
DXP Startup Phases}
\chapter{Liferay Forms}\label{liferay-forms}
The \href{/docs/7-2/user/-/knowledge_base/u/forms}{Liferay Forms}
application is a full-featured form building tool for collecting data.
There's lots of built-in functionality. For the pieces you're missing,
there are extension points.
This section of articles shows developers how to
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Store form entry data in an alternative format. The default storage
type is JSON.
\item
{[}Coming Soon{]} Create new form field types.
\end{enumerate}
\section{Liferay Forms Extension
Points}\label{liferay-forms-extension-points}
Here's a compilation of the Liferay Forms application's extension points
that are ready for your customization:
\begin{itemize}
\tightlist
\item
Create a Form Storage Adapter by implementing a
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/dynamic-data-mapping/dynamic-data-mapping-api/src/main/java/com/liferay/dynamic/data/mapping/storage/StorageAdapter.java}{\texttt{StorageAdapter}}
or by extending the Abstract implementation,
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/dynamic-data-mapping/dynamic-data-mapping-api/src/main/java/com/liferay/dynamic/data/mapping/storage/BaseStorageAdapter.java}{\texttt{BaseStorageAdapter}}.
\item
Create a Form Field Type by implementing a
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/dynamic-data-mapping/dynamic-data-mapping-api/src/main/java/com/liferay/dynamic/data/mapping/form/field/type/DDMFormFieldType.java}{\texttt{DDMFormFieldType}},
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/dynamic-data-mapping/dynamic-data-mapping-api/src/main/java/com/liferay/dynamic/data/mapping/form/field/type/DDMFormFieldTypeSettings.java}{\texttt{DDMFormFieldTypeSettings}},
and a
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/dynamic-data-mapping/dynamic-data-mapping-api/src/main/java/com/liferay/dynamic/data/mapping/form/field/type/DDMFormFieldTemplateContextContributor.java}{\texttt{DDMFormFieldTemplateContextContributor}}.
\item
Create custom validation rules for form fields by implementing a
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/dynamic-data-mapping/dynamic-data-mapping-api/src/main/java/com/liferay/dynamic/data/mapping/form/field/type/DDMFormFieldValueValidator.java}{DDMFormFieldValueValidator}.
\end{itemize}
\chapter{Form Storage Adapters}\label{form-storage-adapters}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
When a User adds a form record, the Forms API routes the processing of
the request through the storage adapter API. The same is true for the
other \emph{CRUD} operations performed on form entries (read, update,
and delete operations). The default implementation of the storage
service is called \texttt{JSONStorageAdapter}, and as its name implies,
it implements the \texttt{StorageAdapter} interface to provide JSON
storage of form entry data.
The Dynamic Data Mapping (DDM) backend can \emph{adapt} to other data
storage formats for form records. Want to store your data in XML? YAML?
No problem. Because the storage API is separated from the regular
service calls used to populate the database table for form entries, a
developer can even choose to store form data outside the Liferay
database.
Define your own format to save form entries by writing your own
implementation of the \texttt{StorageAdapter} interface. The interface
follows the \emph{CRUD} approach, so implementing it requires that you
write methods to create, read, update and delete form values.
\noindent\hrulefill
\textbf{Note:} The \texttt{StorageAdapter} interface and it's abstract
implementation, \textbar{} \texttt{BaseStorageAdapter}, are deprecated
in 7.0. In the future your code should be migrated to implement the
\texttt{DDMStorageAdapter} interface. If you need a storage adapter, the
current extension of \texttt{BaseStorageAdapter} (demonstrated in this
documentation), is still the way to create one, but be aware that it
will not be available in a future version.
\noindent\hrulefill
A newly added storage adapter can only be used with new Forms. All
existing Forms continue to use the adapter selected (JSON by default) at
the time of their creation, and a different storage adapter cannot be
selected.
The example storage adapter in this tutorial serializes form data to be
stored in a simple file, stored on the file system.
\begin{figure}
\centering
\includegraphics{./images/forms-storage-type.png}
\caption{Choose a Storage Type for your form records.}
\end{figure}
\section{Storage Adapter Methods}\label{storage-adapter-methods}
Before handling the CRUD logic, write a \texttt{getStorageType} method.
\begin{description}
\tightlist
\item[\texttt{getStorageType}]
Return a human readable String, as \texttt{getStorageType} determines
what appears in the UI when the form creator is selecting a storage type
for their form. The String value you return here is added to the
\texttt{StorageAdapterRegistry}'s Map of storage adapters.
\end{description}
\section{The CRUD Methods}\label{the-crud-methods}
\texttt{doCreate}: Return a \texttt{long} that identifies each form
record with a unique file ID. Almost as important is to validate the
form values being sent through the storage adapter API. This is as
simple as calling
\texttt{DDMFormValuesValidator.validate(ddmFormValues)}. In addition,
you'll interact with at least two other DDM services to get the form the
values are associated with, and to make sure they're linked:
\texttt{DDMStructureVersionLocalService} and
\texttt{DDMStorageLinkLocalService}. Lastly, the form values in the
\texttt{DDMFormValues} object must be serialized (converted) into the
right storage format. If JSON works for your use case, feel free to use
the \texttt{DDMFormValuesJSONSerializer} service in the Liferay Forms
code, as demonstrated in the following article. Otherwise you'll need to
provide your own serialization service for the form values.
\begin{description}
\tightlist
\item[\texttt{doGetDDMFormValues}]
Return the form values (\texttt{DDMFormValues}) for a form. You'll call
the \texttt{deserialize} method after retrieving them, to take them from
the storage format (e.g., JSON) to a proper \texttt{DDMFormValues}
object. You can use the Liferay Forms
\texttt{DDMFormValuesJSONDeserializer} if you're retrieving JSON data.
\item[\texttt{doUpdate}]
A request to update the values comes from a User in the Liferay Forms
application, so call the validator again, serialize the values into the
proper format, and save them.
\item[\texttt{doDeleteByClass}]
When a delete request is made on a form record directly, delete the form
values in whatever format they're currently being stored in (this is
entirely dependent on your own application of the storage adapter). In
addition, retrieve and delete the DDM class storage link using
\texttt{DDMStorageLinkLocalService}.
\item[\texttt{doDeleteByDDMStrcuture}]
When a delete request is made on an entire form, delete all the form
records associated with it. In addition, take the form's
\texttt{ddmStructureId} and delete all the DDM structure storage links
that were created for it.
\end{description}
\section{Validating Form Entries}\label{validating-form-entries}
Because the Storage Adapter handles User entered data during the
\texttt{add} and \texttt{update} operations, it's important to validate
that the entries include only appropriate data. Add a \texttt{validate}
method to the \texttt{StorageAdapter}, calling the Liferay Forms'
\texttt{DDMFormValuesValidator} method to do the heavy lifting.
\begin{verbatim}
protected void validate(
DDMFormValues ddmFormValues, ServiceContext serviceContext)
throws Exception {
boolean validateDDMFormValues = GetterUtil.getBoolean(
serviceContext.getAttribute("validateDDMFormValues"), true);
if (!validateDDMFormValues) {
return;
}
_ddmFormValuesValidator.validate(ddmFormValues);
}
\end{verbatim}
Make sure to do three things:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Retrieve the value of the \texttt{boolean\ validateDDMFormValues}
attribute from the service context.
\item
If \texttt{validateDDMFormValues} is false, exit the validation
without doing anything.
When a User accesses a form at its dedicated link, there's a periodic
auto-save process of in-progress form values. There's no need to
validate this data until the User hits the \emph{Submit} button on the
form, so the auto-save process sets the \texttt{validateDDMFormValues}
attribute to \texttt{false}.
\item
Otherwise, call the validate method from the
\texttt{DDMFormValuesValidator} service.
\end{enumerate}
All the Java code for the logic discussed here is shown in the next
article,
\href{/docs/7-2/customization/-/knowledge_base/c/creating-a-form-storage-adapter}{Creating
Form Storage Adapters}.
\section{Enabling the Storage
Adapter}\label{enabling-the-storage-adapter}
The storage adapter is enabled at the individual form level. Create a
new form, and select the Storage Adapter \emph{before saving or
publishing the form}. If you wait until first Saving the Form, the
default Storage Adapter is already assigned to the Form, and this
setting is no longer editable.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to the Site Menu → Content → Forms, and click the \emph{Add} button
(\includegraphics{./images/icon-add.png}).
\item
In the Form Builder view, click the \emph{Options} button
(\includegraphics{./images/icon-options.png}) and open the
\emph{Settings} window.
\item
From the select list field called \emph{Select a Storage Type}, choose
the desired type and click \emph{Done}.
\end{enumerate}
Now all the form's entries are stored in the desired format.
\chapter{Creating a Form Storage
Adapter}\label{creating-a-form-storage-adapter}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
There's only one class to create when implementing a Form Storage
Adapter, and it extends the base \texttt{StorageAdapter} implementation.
\begin{verbatim}
@Component(service = StorageAdapter.class)
public class FileSystemStorageAdapter extends BaseStorageAdapter {
\end{verbatim}
The only method without a base implementation in the abstract class is
\texttt{getStorageType}. For file system storage, it can return
\texttt{"File\ System"}.
\begin{verbatim}
@Override
public String getStorageType() {
return "File System";
}
\end{verbatim}
\section{Storage Adapter CRUD
Operations}\label{storage-adapter-crud-operations}
The CRUD operations must be created to properly handle the Form Records.
\section{Create}\label{create}
Next override the \texttt{doCreateMethod} to return a \texttt{long} that
identifies each form record with a unique file ID:
\begin{verbatim}
@Override
protected long doCreate(
long companyId, long ddmStructureId, DDMFormValues ddmFormValues,
ServiceContext serviceContext)
throws Exception {
validate(ddmFormValues, serviceContext);
long fileId = _counterLocalService.increment();
DDMStructureVersion ddmStructureVersion =
_ddmStructureVersionLocalService.getLatestStructureVersion(
ddmStructureId);
long classNameId = PortalUtil.getClassNameId(
FileSystemStorageAdapter.class.getName());
_ddmStorageLinkLocalService.addStorageLink(
classNameId, fileId, ddmStructureVersion.getStructureVersionId(),
serviceContext);
saveFile(
ddmStructureVersion.getStructureVersionId(), fileId, ddmFormValues);
return fileId;
}
@Reference
private CounterLocalService _counterLocalService;
@Reference
private DDMStorageLinkLocalService _ddmStorageLinkLocalService;
@Reference
private DDMStructureVersionLocalService _ddmStructureVersionLocalService;
\end{verbatim}
These are the utility methods invoked in the create method:
\begin{verbatim}
private File getFile(long structureId, long fileId) {
return new File(
getStructureFolder(structureId), String.valueOf(fileId));
}
private File getStructureFolder(long structureId) {
return new File(String.valueOf(structureId));
}
private void saveFile(
long structureVersionId, long fileId, DDMFormValues formValues)
throws IOException {
String serializedDDMFormValues = _ddmFormValuesJSONSerializer.serialize(
formValues);
File formEntryFile = getFile(structureVersionId, fileId);
FileUtil.write(formEntryFile, serializedDDMFormValues);
}
@Reference
private DDMFormValuesJSONSerializer _ddmFormValuesJSONSerializer;
\end{verbatim}
\section{Read}\label{read}
To retrieve the form record's values from the \texttt{File} object where
they were written, override \texttt{doGetDDMFormValues}:
\begin{verbatim}
@Override
protected DDMFormValues doGetDDMFormValues(long classPK) throws Exception {
DDMStorageLink storageLink =
_ddmStorageLinkLocalService.getClassStorageLink(classPK);
DDMStructureVersion structureVersion =
_ddmStructureVersionLocalService.getStructureVersion(
storageLink.getStructureVersionId());
String serializedDDMFormValues = FileUtil.read(
getFile(structureVersion.getStructureVersionId(), classPK));
return _ddmFormValuesJSONDeserializer.deserialize(
structureVersion.getDDMForm(), serializedDDMFormValues);
}
@Reference
private DDMFormValuesJSONDeserializer _ddmFormValuesJSONDeserializer;
\end{verbatim}
\section{Update}\label{update}
Override the \texttt{doUpdate} method so the record's values can be
overwritten. This example calls the \texttt{saveFile} utility method
provided earlier:
\begin{verbatim}
@Override
protected void doUpdate(
long classPK, DDMFormValues ddmFormValues,
ServiceContext serviceContext)
throws Exception {
validate(ddmFormValues, serviceContext);
DDMStorageLink storageLink =
_ddmStorageLinkLocalService.getClassStorageLink(classPK);
saveFile(
storageLink.getStructureVersionId(), storageLink.getClassPK(),
ddmFormValues);
}
\end{verbatim}
\section{Delete}\label{delete}
Override the \texttt{doDeleteByClass} method to delete the \texttt{File}
representing the form record, using the \texttt{classPK}, and to delete
the class storage links:
\begin{verbatim}
@Override
protected void doDeleteByClass(long classPK) throws Exception {
DDMStorageLink storageLink =
_ddmStorageLinkLocalService.getClassStorageLink(classPK);
FileUtil.delete(getFile(storageLink.getStructureId(), classPK));
_ddmStorageLinkLocalService.deleteClassStorageLink(classPK);
}
\end{verbatim}
Provide form record deletion logic to be called when deleting all the
records and storage links associated with a form (using its
\texttt{ddmStructureId}):
\begin{verbatim}
@Override
protected void doDeleteByDDMStructure(long ddmStructureId)
throws Exception {
FileUtil.deltree(getStructureFolder(ddmStructureId));
_ddmStorageLinkLocalService.deleteStructureStorageLinks(ddmStructureId);
}
\end{verbatim}
\section{Beyond CRUD: Validation}\label{beyond-crud-validation}
Add a \texttt{validate} method to the \texttt{StorageAdapter}:
\begin{verbatim}
protected void validate(
DDMFormValues ddmFormValues, ServiceContext serviceContext)
throws Exception {
boolean validateDDMFormValues = GetterUtil.getBoolean(
serviceContext.getAttribute("validateDDMFormValues"), true);
if (!validateDDMFormValues) {
return;
}
_ddmFormValuesValidator.validate(ddmFormValues);
}
\end{verbatim}
Deploy your storage adapter and it's ready to use.
\chapter{Overriding Language Keys}\label{overriding-language-keys}
Core and portlet module \texttt{Language*.properties} files implement
site internationalization. They're fully customizable, too. This section
demonstrates this in the following topics:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{Overriding
Liferay's Language Keys}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys}{Overriding
a Module's Language Keys}
\end{itemize}
\chapter{Overriding Global Language
Keys}\label{overriding-global-language-keys}
Language files contain
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{translations
of your application's user interface messages}. But you can also
override the default language keys globally and in other applications
(including your own). Here are the steps for overriding language keys:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
\hyperref[determine-the-language-keys-to-override]{Determine the
language keys to override}
\item
\hyperref[override-the-keys-in-a-new-language-properties-file]{Override
the keys in a new language properties file}
\item
\hyperref[create-a-resource-bundle-service-component]{Create a
Resource Bundle service component}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} Many applications that were once part of Liferay Portal
6.2 are now modularized. Their language keys might have been moved out
of Liferay's language properties files and into one of the application's
modules. The process for
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys}{overriding
a module's language keys} is different from the process for overriding
Liferay's language keys.
\noindent\hrulefill
\section{Determine the language keys to
override}\label{determine-the-language-keys-to-override}
So how do you find global language keys? They're in the
\texttt{Language{[}xx\_XX{]}.properties} files in the source code or
your bundle.
\begin{itemize}
\item
From the source:
\texttt{/portal-impl/src/content/Language{[}xx\_XX{]}.properties}
\item
From a bundle:
\texttt{portal-impl.jar}
\end{itemize}
All language properties files contain properties you can override, like
the language settings properties:
\begin{verbatim}
##
## Language settings
##
...
lang.user.name.field.names=prefix,first-name,middle-name,last-name,suffix
lang.user.name.prefix.values=Dr,Mr,Ms,Mrs
lang.user.name.required.field.names=last-name
lang.user.name.suffix.values=II,III,IV,Jr,Phd,Sr
...
\end{verbatim}
There are also many simple keys you can override to update default
messages and labels.
\begin{verbatim}
##
## Category titles
##
category.admin=Admin
category.alfresco=Alfresco
category.christianity=Christianity
category.cms=Content Management
...
\end{verbatim}
For example, Figure 1 shows a button that uses Liferay's
\texttt{publish} default language key.
\begin{verbatim}
`publish=Publish`
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/standard-publish.png}
\caption{Messages displayed in Liferay's user interface can be
customized.}
\end{figure}
Next, you'll learn how to override this key.
\section{Override the keys in a new language properties
file}\label{override-the-keys-in-a-new-language-properties-file}
Once you know the keys to override, create a language properties file
for the locale you want (or the default \texttt{Language.properties}
file) in your module's \texttt{src/main/resources/content} folder. In
your file, define the keys your way. For example, you could override the
\texttt{publish} key.
\begin{verbatim}
publish=Publish Override
\end{verbatim}
To enable your change, you must create a resource bundle service
component to reference your language file.
\section{Create a Resource Bundle service
component}\label{create-a-resource-bundle-service-component}
In your module, create a class that extends
\texttt{java.util.ResourceBundle} for the locale you're overriding.
Here's an example resource bundle class for the \texttt{en\_US} locale:
\begin{verbatim}
@Component(
property = { "language.id=en_US" },
service = ResourceBundle.class
)
public class MyEnUsResourceBundle extends ResourceBundle {
@Override
protected Object handleGetObject(String key) {
return _resourceBundle.getObject(key);
}
@Override
public Enumeration getKeys() {
return _resourceBundle.getKeys();
}
private final ResourceBundle _resourceBundle = ResourceBundle.getBundle(
"content.Language_en_US", UTF8Control.INSTANCE);
}
\end{verbatim}
The class's \texttt{\_resourceBundle} field is assigned a
\texttt{ResourceBundle}. The call to \texttt{ResourceBundle.getBundle}
needs two parameters. The \texttt{content.Language\_en\_US} parameter is
the language file's qualified name with respect to the module's
\texttt{src/main/resources} folder. The second parameter is a
\texttt{control} that sets the language syntax of the resource bundle.
To use language syntax identical to Liferay's syntax, import Liferay's
\texttt{com.liferay.portal.kernel.language.UTF8Control} class and set
the second parameter to \texttt{UTF8Control.INSTANCE}.
The class's \texttt{@Component} annotation declares it an OSGi
\texttt{ResourceBundle} service component. It's \texttt{language.id}
property designates it for the \texttt{en\_US} locale.
\begin{verbatim}
@Component(
property = { "language.id=en_US" },
service = ResourceBundle.class
)
\end{verbatim}
The class overrides these methods:
\begin{itemize}
\item
\textbf{\texttt{handleGetObject}:} Looks up the key in the module's
resource bundle (which is based on the module's language properties
file) and returns the key's value as an \texttt{Object}.
\item
\textbf{\texttt{getKeys}:} Returns an \texttt{Enumeration} of the
resource bundle's keys.
\end{itemize}
Your resource bundle service component redirects the default language
keys to your module's language key overrides.
\noindent\hrulefill
\textbf{Note}: Global language key overrides for multiple locales
require a separate module for each locale. Each module's
\texttt{ResourceBundle} extension class (like the
\texttt{MyEnUsResourceBundle} class above) must specify its locale in
the \texttt{language.id} component property definition and in the
language file qualified name parameter. For example, here is what they
look like for the Spanish locale.
Component definition:
\begin{verbatim}
@Component(
property = { "language.id=es_ES" },
service = ResourceBundle.class
)
\end{verbatim}
Resource bundle assignment:
\begin{verbatim}
private final ResourceBundle _resourceBundle = ResourceBundle.getBundle(
"content.Language_es_ES", UTF8Control.INSTANCE);
\end{verbatim}
\noindent\hrulefill
\textbf{Important}: If your module
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module}{uses
language keys from another module} and
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module}{overrides
any of that other module's keys}, make sure to use OSGi headers to
specify the capabilities your module requires and provides. This lets
you prioritize resource bundles from the modules.
To see your Liferay language key overrides in action,
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploy
your module} and visit the portlets and pages that use the keys.
\begin{figure}
\centering
\includegraphics{./images/localized-publish.png}
\caption{This button uses the overridden \texttt{publish} key.}
\end{figure}
That's all there is to overriding Liferay's language keys.
\section{Related Topics}\label{related-topics-25}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-core-language-key-hooks}{Upgrading
Core Language Key Hooks}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys}{Overriding
a Module's Language Keys}
\end{itemize}
\chapter{Overriding a Module's Language
Keys}\label{overriding-a-modules-language-keys}
What do you do if the language keys you want to modify are in one of
Liferay's applications or another module whose source code you don't
control? Since module language keys are in the respective module, the
process for overriding a module's language keys is different from
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys}{the
process of overriding Liferay's language keys}.
Here is the process:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
\hyperref[find-the-module-and-its-metadata-and-language-keys]{Find the
module and its metadata and language keys}
\item
\hyperref[write-custom-language-key-values]{Write your custom language
key values}
\item
\hyperref[prioritize-your-modules-resource-bundle]{Prioritize your
module's resource bundle}
\end{enumerate}
\section{Find the module and its metadata and language
keys}\label{find-the-module-and-its-metadata-and-language-keys}
In
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell}, list the bundles and grep for keyword(s) that match the
portlet's display name. Language keys are in the portlet's web module
(bundle). When you find the bundle, note its ID number.
To find the Blogs portlet, for example, your Gogo commands and output
might look like this:
\begin{verbatim}
g! lb | grep Blogs
152|Active | 1|Liferay Blogs Service (1.0.2)
184|Active | 1|Liferay Blogs Editor Config (2.0.1)
202|Active | 1|Liferay Blogs Layout Prototype (2.0.2)
288|Active | 1|Liferay Blogs Recent Bloggers Web (1.0.2)
297|Active | 1|Liferay Blogs Item Selector Web (1.0.2)
374|Active | 1|Liferay Blogs Item Selector API (2.0.1)
448|Active | 1|Liferay Blogs API (3.0.1)
465|Active | 1|Liferay Blogs Web (1.0.6)
true
\end{verbatim}
List the bundle's headers by passing its ID to the \texttt{headers}
command.
\begin{verbatim}
g! headers 465
Liferay Blogs Web (465)
-----------------------
Manifest-Version = 1.0
Bnd-LastModified = 1459866186018
Bundle-ManifestVersion = 2
Bundle-Name = Liferay Blogs Web
Bundle-SymbolicName = com.liferay.blogs.web
Bundle-Version: 1.0.6
...
Web-ContextPath = /blogs-web
g!
\end{verbatim}
Note the \texttt{Bundle-SymbolicName}, \texttt{Bundle-Version}, and
\texttt{Web-ContextPath}. The \texttt{Web-ContextPath} value, following
the \texttt{/}, is the servlet context name.
\textbf{Important}: Record the servlet context name, bundle symbolic
name and version, as you'll use them to create the resource bundle
loader later in the process.
For example, here are those values for Liferay Blogs Web module:
\begin{itemize}
\tightlist
\item
Bundle symbolic name: \texttt{com.liferay.blogs.web}
\item
Bundle version: \texttt{4.0.16}
\item
Servlet context name: \texttt{blogs-web}
\end{itemize}
Next find the module's JAR file so you can examine its language keys.
Liferay follows this module JAR file naming convention:
\begin{verbatim}
[bundle symbolic name]-[version].jar
\end{verbatim}
For example, the Blogs Web version 4.0.16 module is in
\texttt{com.liferay.blogs.web-4.0.16.jar}.
Here's where to find the module JAR:
\begin{itemize}
\tightlist
\item
Liferay's
\href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/}{Nexus
repository}
\item
\texttt{{[}Liferay\ Home{]}/osgi/modules}
\item
Embedded in an application's or application suite's LPKG file in
\texttt{{[}Liferay\ \ \ \ \ Home{]}/osgi/marketplace}.
\end{itemize}
The language property files are in the module's
\texttt{src/main/resources/content} folder. Identify the language keys
you want to override in the \texttt{Language{[}\_xx{]}.properties}
files.
Checkpoint: Make sure you have the required information for overriding
the module's language keys:
\begin{itemize}
\tightlist
\item
Language keys
\item
Bundle symbolic name
\item
Servlet context name
\end{itemize}
Next you'll write new values for the language keys.
\section{Write custom language key
values}\label{write-custom-language-key-values}
Create a new module to hold a resource bundle loader and your custom
language keys.
In your module's \texttt{src/main/resources/content} folder, create
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{language
properties files} for each locale whose keys you want to override. In
each language properties file, specify your language key overrides.
Next you'll prioritize your module's language keys as a resource bundle
for the target module.
\section{Prioritize Your Module's Resource
Bundle}\label{prioritize-your-modules-resource-bundle}
Now that your language keys are in place, use OSGi manifest headers to
specify the language keys are for the target module. To compliment the
target module's resource bundle, you'll aggregate your resource bundle
with the target module's resource bundle. You'll list your module first
to prioritize its resource bundle over the target module resource
bundle. Here's an example of module
\texttt{com.liferay.docs.l10n.myapp.lang} prioritizing its resource
bundle over target module \texttt{com.liferay.blogs.web}'s resource
bundle:
\begin{verbatim}
Provide-Capability:\
liferay.resource.bundle;resource.bundle.base.name="content.Language",\
liferay.resource.bundle;resource.bundle.aggregate:String="(bundle.symbolic.name=com.liferay.docs.l10n.myapp.lang),(bundle.symbolic.name=com.liferay.blogs.web)";bundle.symbolic.name=com.liferay.blogs.web;resource.bundle.base.name="content.Language";service.ranking:Long="2";\
servlet.context.name=blogs-web
\end{verbatim}
The example \texttt{Provide-Capability} header has two parts:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\texttt{liferay.resource.bundle;resource.bundle.base.name="content.Language"}
declares that the module provides a resource bundle with the base name
\texttt{content.language}.
\item
The
\texttt{liferay.resource.bundle;resource.bundle.aggregate:String=...}
directive specifies the list of bundles with resource bundles to
aggregate, the target bundle, the target bundle's resource bundle
name, and this service's ranking:
\begin{itemize}
\tightlist
\item
\texttt{"(bundle.symbolic.name=com.liferay.docs.l10n.myapp.lang),(bundle.symbolic.name=com.liferay.blogs.web)"}:
The service aggregates resource bundles from bundles
\texttt{com.liferay.docs.l10n.myapp.lang} and
\texttt{com.liferay.blogs.web}. Aggregate as many bundles as
desired. Listed bundles are prioritized in descending order.
\item
\texttt{bundle.symbolic.name=com.liferay.blogs.web;resource.bundle.base.name="content.Language"}:
Override the \texttt{com.liferay.blogs.web} bundle's resource bundle
named \texttt{content.Language}.
\item
\texttt{service.ranking:Long="2"}: The resource bundle's service
ranking is \texttt{2}. The OSGi framework applies this service if it
outranks all other resource bundle services that target
\texttt{com.liferay.blogs.web}'s \texttt{content.Language} resource
bundle.
\item
\texttt{servlet.context.name=blogs-web}: The target resource bundle
is in servlet context \texttt{blogs-web}.
\end{itemize}
\end{enumerate}
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy
your module} to see the language keys you've overridden.
\noindent\hrulefill
\textbf{Tip:} If your override isn't showing, use
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
Shell} to check for competing resource bundle services. It may be that
another service outranks yours. To check for competing resource bundle
services whose aggregates include \texttt{com.liferay.blogs.web}'s
resource bundle, for example, execute this Gogo Shell command:
\begin{verbatim}
services "(bundle.symbolic.name=com.liferay.login.web)"
\end{verbatim}
Search the results for resource bundle aggregate services whose ranking
is higher.
\noindent\hrulefill
Now you can modify the language keys of modules in Liferay's OSGi
runtime. Remember, language keys you want to override might actually be
in Liferay's core. You can
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{override
Liferay's language keys} too.
\section{Related Topics}\label{related-topics-26}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-core-language-key-hooks}{Upgrading
Core Language Key Hooks}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys}{Overriding
Global Language Keys}
\end{itemize}
\chapter{Overriding Liferay Services (Service
Wrappers)}\label{overriding-liferay-services-service-wrappers}
Why might you need to customize Liferay services? Perhaps you've added a
new field to Liferay's \texttt{User} object and you want its value to be
saved whenever the \texttt{addUser} or \texttt{updateUser} methods of
Liferay's API are called. Or maybe you want to add some additional
logging functionality to some Liferay APIs or other services built using
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}. Whatever your case may be, Liferay's service wrappers provide
easy-to-use extension points for customizing Liferay's services.
To create a module that overrides one of Liferay's services, use
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} to
create a \texttt{servicewrapper} project type with the command below
(replace the class and package names with your own):
\begin{verbatim}
blade create -t service-wrapper -p com.liferay.docs.serviceoverride
-c UserLocalServiceOverride -s
com.liferay.portal.kernel.service.UserLocalServiceWrapper service-override
\end{verbatim}
As an example, here's the \texttt{UserLocalServiceOverride} class that's
generated with the Service Wrapper Template:
\begin{verbatim}
package com.liferay.docs.serviceoverride;
import com.liferay.portal.kernel.service.UserLocalServiceWrapper;
import com.liferay.portal.kernel.service.ServiceWrapper;
import org.osgi.service.component.annotations.Component;
@Component(
immediate = true,
property = {
},
service = ServiceWrapper.class
)
public class UserLocalServiceOverride extends UserLocalServiceWrapper {
public UserLocalServiceOverride() {
super(null);
}
}
\end{verbatim}
Notice that you must specify the fully qualified class name of the
service wrapper class that you want to extend. The \texttt{service}
argument was used in full in this import statement:
\begin{verbatim}
import com.liferay.portal.service.UserLocalServiceWrapper;
\end{verbatim}
This import statement, in turn, allowed the short form of the service
wrapper class name to be used in the class declaration of your component
class:
\begin{verbatim}
public class UserLocalServiceOverride extends UserLocalServiceWrapper {...}
\end{verbatim}
The bottom line is that when using \texttt{blade\ create} to create a
service wrapper project, you must specify a fully qualified class name
as the \texttt{service} argument. (This is also true when using
\texttt{blade\ create} to create a service project.) For information
about creating service projects, please see
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}.
The generated \texttt{UserLocalServiceOverride} class does not actually
customize any Liferay service. Before you can test that your service
wrapper module actually works, you need to override at least one service
method.
Open your \texttt{UserLocalServiceOverride} class and add the following
methods:
\begin{verbatim}
@Override
public int authenticateByEmailAddress(long companyId, String emailAddress,
String password, Map headerMap,
Map parameterMap, Map resultsMap)
throws PortalException {
System.out.println(
"Authenticating user by email address " + emailAddress);
return super.authenticateByEmailAddress(companyId, emailAddress, password,
headerMap, parameterMap, resultsMap);
}
@Override
public User getUser(long userId) throws PortalException {
System.out.println("Getting user by id " + userId);
return super.getUser(userId);
}
\end{verbatim}
Each of these methods overrides a Liferay service method. These
implementations merely execute a few print statements that before
executing the original service implementations.
Lastly, you must add the following method to the bottom of your service
wrapper so it can find the appropriate service it's overriding on
deployment.
\begin{verbatim}
@Reference(unbind = "-")
private void serviceSetter(UserLocalService userLocalService) {
setWrappedService(userLocalService);
}
\end{verbatim}
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Build
and deploy your module}. Congratulations! You've created and deployed a
Liferay service wrapper!
\section{Related Topics}\label{related-topics-27}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-service-wrapper-hooks}{Upgrading
Service Wrappers}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-blade-cli}{Installing
Blade CLI}
\item
\href{/docs/7-1/tutorials/-/knowledge_base/t/creating-projects-with-blade-cli}{Creating
Projects with Blade CLI}
\end{itemize}
\chapter{Overriding lpkg Files}\label{overriding-lpkg-files}
Applications are delivered through Liferay Marketplace as \emph{lpkg}
files. This is a simple compressed file format that contains .jar files
for deploying to Liferay DXP. If you want to examine an application from
Marketplace, all you have to do is unzip its .lpkg file to reveal its
.jar files.
After examining an application, you may want to
\href{/docs/7-2/customization/-/knowledge_base/c/liferay-customization}{customize}
one of its .jars. Make your customization in a copy of the .jar, but
don't deploy it the way you'd normally deploy an application. By
overriding the .lpkg file, you can update application modules without
modifying the original .lpkg file. Here are the steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Shut down Liferay DXP.
\item
Create a folder called \texttt{override} in the
\href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\texttt{Liferay\ Home{]}/osgi/marketplace}
folder}.
\item
Name your updated .jar the same as the .jar in the original .lpkg,
minus the version information. For example, if you're overriding the
\texttt{com.liferay.amazon.rankings.web-1.0.5.jar} from the
\texttt{Liferay\ CE\ Amazon\ \ \ \ \ \ Rankings.lpkg}, you'd name your
.jar \texttt{com.liferay.amazon.rankings.web.jar}.
\item
Copy this .jar into the \texttt{override} folder you created in step
one.
\end{enumerate}
This works for applications from Marketplace, but there's also the
static .lpkg that contains core Liferay technology and third-party
utilities (such as the servlet API, Apache utilities, etc.). To
customize or patch any of these .jar files, follow this process:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make your customization and package it in a .jar file.
\item
Name your .jar the same as the original .jar, minus the version
information. For example, a customized
\texttt{com.liferay.portal.profile-1.0.4.jar} should be
\texttt{com.liferay.portal.profile.jar}.
\item
Copy the .jar into the \texttt{{[}Liferay\ Home{]}/osgi/static}
folder.
\end{enumerate}
Now start Liferay DXP. Note that any time you add and remove .jars this
way, Liferay DXP must be shut down and then restarted for the changes to
take effect.
If you must roll back your customizations, delete the overriding .jar
files: Liferay DXP uses the original .jar on its next startup.
\chapter{Overriding Liferay MVC
Commands}\label{overriding-liferay-mvc-commands}
MVC Commands are used to break up the controller layer of
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC applications} into smaller, more digestible code chunks.
Sometimes you'll want to override an MVC command, whether it's in a
Liferay application or another Liferay MVC application whose source code
you don't own. Since MVC commands are components registered in the OSGi
runtime, you can simply publish your own customization of the component,
give it a higher service ranking, and deploy it.
All existing components that reference the original MVC command service
component (using a greedy reference policy) switch to reference your new
one. Any existing
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-osgi-services}{reluctant
references to the original command must be configured to reference the
new one}. Once they're configured with the new service component, their
JSP's command URLs invoke the new custom MVC command.
Here are the customization options available for each Liferay MVC
Command type:
\begin{itemize}
\tightlist
\item
MVCActionCommand:
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-mvcactioncommand}{Add
logic}
\item
MVCRenderCommand:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand\#adding-logic-to-an-existing-mvc-render-command}{Add
logic}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand\#redirecting-to-a-new-jsp}{Redirect
to a different JSP}
\end{itemize}
\item
MVCResourceCommand:
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-mvcresourcecommand}{Add
logic}
\end{itemize}
This section demonstrates each MVC command customization option. Since
the steps for adding logic are generally the same across MVC command
types, start with
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands}{adding
logic}.
\chapter{Adding Logic to MVC
Commands}\label{adding-logic-to-mvc-commands}
You can completely override MVC commands, or any OSGi service for that
matter, but \emph{adding logic} to the commands is the better option.
Discarding necessary logic is bad. Conversely any logic you copy from
the original might not work in new versions of the portlet. Adding
custom logic while continuing to invoke the original logic decouples the
custom class from the original implementation. Keeping the new logic
separate form the original logic keeps the code clean, maintainable, and
easy to understand.
Here are the steps for adding logic to MVC commands:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
\hyperref[step-1-implement-the-interface]{Implement the interface}
\item
\hyperref[step-2-publish-as-a-component]{Publish as a component}
\item
\hyperref[step-3-refer-to-the-original-implementation]{Refer to the
original implementation}
\item
\hyperref[step-4-add-the-logic]{Add the logic, and call the original}
\end{enumerate}
\section{Step 1: Implement the
interface}\label{step-1-implement-the-interface}
Implement the respective MVC Command interface either directly or by
extending an existing base class that implements it. Extending a base
class for the interface relieves you from implementing logic that should
typically be a part of most command implementations. For example, to add
logic to the Blogs portlet's \texttt{EditEntryMVCActionCommand}, you
would extend base class \texttt{BaseMVCActionCommand}.
\begin{verbatim}
public class CustomBlogsMVCActionCommand extends BaseMVCActionCommand {...}
\end{verbatim}
Check the MVC command interfaces for existing base classes:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCActionCommand.html}{\texttt{MVCActionCommand}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html}{\texttt{MVCRenderCommand}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCResourceCommand.html}{\texttt{MVCResourceCommand}}
\end{itemize}
Next make your class a service component.
\section{Step 2: Publish as a
component}\label{step-2-publish-as-a-component}
The Declarative Services \texttt{@Component} annotation facilitates
customizing MVC commands. All the customization options require
publishing your MVC command class as a component. For example, this
\texttt{@Component} annotation declares an \texttt{MVCActionCommand}
service.
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN,
"mvc.command.name=/blogs/edit_entry",
"service.ranking:Integer=100"
},
service = MVCActionCommand.class
)
public class CustomBlogsMVCActionCommand extends BaseMVCActionCommand {
...
}
\end{verbatim}
It publishes \texttt{CustomBlogsMVCActionCommand} as a service component
for the \texttt{MVCActionCommand} class. Upon resolving, it's activated
immediately because \texttt{immediate\ =\ true}. The component is
invoked in the Blogs Admin portlet by the command URL
\texttt{/blogs/edit\_entry}. Its service ranking of \texttt{100}
prioritizes it ahead of the original service component, whose ranking is
\texttt{0}.
Here's what you need to specify in an \texttt{@Component} annotation for
your custom MVC command:
\begin{itemize}
\item
\texttt{javax.portlet.name}: for each portlet you want the
customization to affect. JSPs in these portlets can invoke the MVC
command via applicable command URL tags. You can specify the same
portlets as the original MVC command or a subset of those portlets.
\item
\texttt{mvc.command.name}: this property declares the command URL that
maps to this custom MVC command component.
\item
\texttt{service.ranking:Integer}: set this property to a higher
integer than the original service implementation's ranking. The
ranking tells the OSGi runtime which service to use, in cases where
multiple components register the same service, with the same
properties. The higher the integer you specify here, the more weight
your component carries. Liferay's service implementations typically
have a \texttt{0} ranking.
\item
\texttt{service}: this attribute specifies the service (interface) to
override.
\item
\texttt{immediate}: set this attribute to \texttt{true} to activate
your component immediately upon resolution.
\end{itemize}
You can refer back to this list as you add \texttt{@Component}
annotations to your custom MVC commands.
Next reference the original implementation.
\section{Step 3: Refer to the original
implementation}\label{step-3-refer-to-the-original-implementation}
Use a field annotated with \texttt{@Reference} to fetch a reference to
the original MVC command component. If there are no additional
customizations on the original component, this reference will be for the
original MVC command type. For example, this field references the
original MVC command component \texttt{EditEntryMVCActionCommand}.
\begin{verbatim}
@Reference(
target = "(component.name=com.liferay.blogs.web.internal.portlet.action.EditEntryMVCActionCommand)")
protected MVCActionCommand mvcActionCommand;
\end{verbatim}
Here's how to add the reference:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Declare the field as the MVC command interface type that it is. For
example, the \texttt{mvcActionCommand} field is type
\texttt{MVCActionCommand}.
\item
Add the \texttt{@Reference} annotation.
\item
In the annotation, define a \texttt{target} attribute that filters on
a \texttt{component.name} equal to the default service implementation
class's fully qualified name.
\end{enumerate}
When your custom component resolves, the OSGi runtime assigns the
targeted service to your field. It's time to add your custom logic.
\section{Step 4: Add the logic}\label{step-4-add-the-logic}
Adding the logic involves overriding the primary method of the base
class you're extending or the interface you're implementing. In your
method override, add your new logic AND then invoke the original
implementation. For example, the following method overrides
\texttt{BaseMVCActionCommand}'s method \texttt{doProcessAction}.
\begin{verbatim}
@Override
protected void doProcessAction(
ActionRequest actionRequest, ActionResponse actionResponse)
throws Exception {
// Add custom logic here
...
// Call the original service implementation
mvcActionCommand.processAction(actionRequest, actionResponse);
}
\end{verbatim}
The method above defines custom logic and then invokes the original
service it referenced in the previous step.
If you use this approach, your extension will continue to work with new
versions of the original portlet, because no coupling exists between the
original portlet logic and your customization. The command
implementation class can change. Make sure to keep your reference
updated to the name of the current implementation class.
Congratulations on adding logic to your existing MVC command.
\chapter{Overriding
MVCRenderCommands}\label{overriding-mvcrendercommands}
You can override
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html}{\texttt{MVCRenderCommand}}
for any portlet that uses Liferay's MVC framework and publishes an
\texttt{MVCRenderCommand} component.
For example, Liferay's Blogs application has a class called
\texttt{EditEntryMVCRenderCommand}, with this component:
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + BlogsPortletKeys.BLOGS,
"javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN,
"javax.portlet.name=" + BlogsPortletKeys.BLOGS_AGGREGATOR,
"mvc.command.name=/blogs/edit_entry"
},
service = MVCRenderCommand.class
)
\end{verbatim}
This MVC render command can be invoked from any of the portlets
specified by the \texttt{javax.portlet.name} parameter, by calling a
render URL that names the MVC command:
\begin{verbatim}
\end{verbatim}
What if you want to override the command, but not for all of the
portlets listed in the original component? In your override component,
just list the \texttt{javax.portlet.name} of the portlets where you want
the override to take effect. For example, if you want to override the
\texttt{/blogs/edit\_entry} MVC render command just for the Blogs Admin
portlet (the Blogs Application accessed in the site administration
section of Liferay), your component could look like this:
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN,
"mvc.command.name=/blogs/edit_entry",
"service.ranking:Integer=100"
},
service = MVCRenderCommand.class
)
\end{verbatim}
Note the last property listed, \texttt{service.ranking}. It's used to
tell the OSGi runtime which service to use, in cases where there are
multiple components registering the same service, with the same
properties. The higher the integer you specify here, the more weight
your component carries. In this case, the override component is used
instead of the original one, since the default value for this property
is \texttt{0}.
After that, it's up to you to do whatever you'd like. MVC render
commands can be customized for these purposes:
\begin{itemize}
\tightlist
\item
\hyperref[adding-logic-to-an-existing-mvc-render-command]{Adding Logic
to an Existing MVC Render Command}
\item
\hyperref[redirecting-to-a-new-jsp]{Redirecting to a new JSP}
\end{itemize}
Start by exploring how to add logic to an existing MVC render command.
\section{Adding Logic to an Existing MVC Render
Command}\label{adding-logic-to-an-existing-mvc-render-command}
You can add logic to an MVC render command following the
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands}{general
steps for MVC commands}. Specifically for MVC render commands, you must
directly implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html}{\texttt{MVCRenderCommand}
interface} and override its \texttt{render} method.
For example, this custom MVC render command has a placeholder (i.e., at
comment \texttt{//Do\ something\ here}) for adding logic to the
\texttt{render} method:
\begin{verbatim}
public CustomEditEntryRenderCommand implements MVCRenderCommand {
@Override
public String render(RenderRequest renderRequest,
RenderResponse renderResponse)
throws PortletException {
//Do something here
return mvcRenderCommand.render(renderRequest, renderResponse);
}
@Reference(target =
"(component.name=com.liferay.blogs.web.internal.portlet.action.EditEntryMVCRenderCommand)")
protected MVCRenderCommand mvcRenderCommand;
}
\end{verbatim}
The example references an \texttt{EditEntryMVCRenderCommand}
implementation of \texttt{MVCRenderCommand}. In the \texttt{render}
method, you'd replace the placeholder with new logic and then invoke the
original implementation's logic by calling its \texttt{render} method.
Sometimes, you might need to redirect the request to an entirely new
JSP. You can do that from a custom MVC render command module too.
\section{Redirecting to a New JSP}\label{redirecting-to-a-new-jsp}
\texttt{MVCRenderCommand}'s \texttt{render} method returns a JSP path as
a String. By default, the JSP must live in the original module, so you
cannot simply specify a path to a custom JSP in your override module. To
redirect it to a JSP in your new module, you must make the method skip
dispatching to the original JSP altogether, by using the constant
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderConstants.html}{\texttt{MVCRenderConstants.MVC\_PATH\_VALUE\_SKIP\_DISPATCH}
class}. Then you need to initiate your own dispatching process,
directing the request to your JSP path. Here's how that might look in
practice:
\begin{verbatim}
public class CustomEditEntryMVCRenderCommand implements MVCRenderCommand {
@Override
public String render(
RenderRequest renderRequest, RenderResponse renderResponse) throws
PortletException {
System.out.println("Rendering custom_edit_entry.jsp");
RequestDispatcher requestDispatcher =
servletContext.getRequestDispatcher("/custom_edit_entry.jsp");
try {
HttpServletRequest httpServletRequest =
PortalUtil.getHttpServletRequest(renderRequest);
HttpServletResponse httpServletResponse =
PortalUtil.getHttpServletResponse(renderResponse);
requestDispatcher.include
(httpServletRequest, httpServletResponse);
} catch (Exception e) {
throw new PortletException
("Unable to include custom_edit_entry.jsp", e);
}
return MVCRenderConstants.MVC_PATH_VALUE_SKIP_DISPATCH;
}
@Reference(target = "(osgi.web.symbolicname=com.custom.code.web)")
protected ServletContext servletContext;
}
\end{verbatim}
The servlet context provides access to the request dispatcher. A servlet
context is automatically created for portlets. It can be created for
other modules by including the following line in your \texttt{bnd.bnd}
file:
\begin{verbatim}
Web-ContextPath: /custom-code-web
\end{verbatim}
Follow these steps to fetch the portlet's servlet context in your custom
MVC render command:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add a \texttt{ServletContext} field.
\begin{verbatim}
protected ServletContext servletContext;
\end{verbatim}
\item
Add the \texttt{@Reference} annotation to the field and set the
annotation to filter on the portlet's module. By convention, Liferay
puts portlets in modules whose symbolic names end in \texttt{.web}.
For example, this servlet context reference filters on a module whose
symbolic name is \texttt{com.custom.code.web}.
\begin{verbatim}
@Reference(target = "(osgi.web.symbolicname=com.custom.code.web)")
protected ServletContext servletContext;
\end{verbatim}
\end{enumerate}
Implement your \texttt{render} method this way:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a request dispatcher to your module's custom JSP:
\begin{verbatim}
RequestDispatcher requestDispatcher =
servletContext.getRequestDispatcher("/custom_edit_entry.jsp");
\end{verbatim}
\item
Include the HTTP servlet request and response in the request
dispatcher.
\begin{verbatim}
try {
HttpServletRequest httpServletRequest =
PortalUtil.getHttpServletRequest(renderRequest);
HttpServletResponse httpServletResponse =
PortalUtil.getHttpServletResponse(renderResponse);
requestDispatcher.include
(httpServletRequest, httpServletResponse);
} catch (Exception e) {
throw new PortletException
("Unable to include custom_edit_entry.jsp", e);
}
\end{verbatim}
\item
Return the request dispatcher via the constant
\texttt{MVC\_PATH\_VALUE\_SKIP\_DISPATCH}.
\begin{verbatim}
return MVCRenderConstants.MVC_PATH_VALUE_SKIP_DISPATCH;
\end{verbatim}
\end{enumerate}
After deploying your module, the
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands\#step-2-publish-as-a-component}{portlets
targeted by your custom \texttt{MVCRenderCommand} component} render your
new JSP.
\section{Related Topics}\label{related-topics-28}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands}{Adding
Logic to MVC Commands}
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-struts-action-hooks}{Converting
StrutsActionWrappers to MVCCommands}
\end{itemize}
\chapter{Overriding
MVCActionCommands}\label{overriding-mvcactioncommands}
In case you want add to a Liferay MVC action command, you can. The OSGi
framework lets you override MVC action commands if you follow the
instructions for
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands}{adding
logic to MVC commands}. It involves
\href{/docs/7-1/tutorials/-/knowledge_base/t/adding-logic-to-mvc-commands\#publish-as-a-component}{registering
your custom MVC action command as an OSGi component} with the same
properties as the original, but with a higher service ranking.
Custom MVC action commands typically extend the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/BaseMVCActionCommand.html}{\texttt{BaseMVCActionCommand}
class}, and override its \texttt{doProcessAction} method, which returns
\texttt{void}. Add your logic to the original behavior of the action
method by getting a reference to the original service, and calling it
after your own logic.
For example, this \texttt{MVCActionCommand} override checks whether the
\texttt{delete} action is invoked on a blog entry, and prints a message
to the log, before continuing with the original processing:
\begin{verbatim}
@Component(
property = {
"javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN,
"mvc.command.name=/blogs/edit_entry",
"service.ranking:Integer=100"
},
service = MVCActionCommand.class
)
public class CustomBlogsMVCActionCommand extends BaseMVCActionCommand {
@Override
protected void doProcessAction
(ActionRequest actionRequest, ActionResponse actionResponse)
throws Exception {
String cmd = ParamUtil.getString(actionRequest, Constants.CMD);
if (cmd.equals(Constants.DELETE)) {
System.out.println("Deleting a Blog Entry");
}
mvcActionCommand.processAction(actionRequest, actionResponse);
}
@Reference(
target = "(component.name=com.liferay.blogs.web.internal.portlet.action.EditEntryMVCActionCommand)")
protected MVCActionCommand mvcActionCommand;
}
\end{verbatim}
Adding MVC action command logic before existing logic is straightforward
and maintains loose coupling between new and old code.
\section{Related Topics}\label{related-topics-29}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands}{Adding
Logic to MVC Commands}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand}{Overriding
MVCRenderCommands}
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-struts-action-hooks}{Converting
StrutsActionWrappers to MVCCommands}
\end{itemize}
\chapter{Overriding
MVCResourceCommands}\label{overriding-mvcresourcecommands}
If you need to add functionality to a Liferay MVC resource command, you
can. The Liferay MVC command framework supports customizing MVC resource
commands. It follows the process for
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands}{adding
logic to MVC commands} and it is similar to the ones described for
\texttt{MVCRenderCommand} and \texttt{MVCActionCommand}. There's a
couple things to keep in mind:
\begin{itemize}
\item
The service to specify in your component is
\texttt{MVCResourceCommand.class}
\item
As with overriding \texttt{MVCRenderCommand}, there's no base
implementation class to extend. Implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCResourceCommand.html}{\texttt{MVCResourceCommand}
interface} yourself.
\item
Keep your code decoupled from the original code by adding your logic
to the original \texttt{MVCResourceCommand}'s logic by getting a
reference to the original and returning a call to its
\texttt{serveResource} method:
\end{itemize}
\begin{verbatim}
return mvcResourceCommand.serveResource(resourceRequest, resourceResponse);
\end{verbatim}
The following example overrides the behavior of
\texttt{com.liferay.login.web.portlet.action.CaptchaMVCResourceCommand},
from the Liferay's Login portlet's \texttt{login-web} module. It simply
prints a line in the console and then executes the original logic:
returning the Captcha image for the account creation screen.
\begin{verbatim}
@Component(
property = {
"javax.portlet.name=" + LoginPortletKeys.LOGIN,
"mvc.command.name=/login/captcha"
},
service = MVCResourceCommand.class
)
public class CustomCaptchaMVCResourceCommand implements MVCResourceCommand {
@Override
public boolean serveResource
(ResourceRequest resourceRequest, ResourceResponse resourceResponse) {
System.out.println("Serving login captcha image");
return mvcResourceCommand.serveResource(resourceRequest, resourceResponse);
}
@Reference(target =
"(component.name=com.liferay.login.web.internal.portlet.action.CaptchaMVCResourceCommand)")
protected MVCResourceCommand mvcResourceCommand;
}
\end{verbatim}
And that, as they say, is that. Even if you don't own the source code of
an application, you can
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-liferay-mvc-commands}{override
its MVC commands} just by knowing the component class name.
\section{Related Topics}\label{related-topics-30}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands}{Adding
Logic to MVC Commands}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand}{Overriding
MVCRenderCommands}
\end{itemize}
\chapter{Overriding OSGi Services}\label{overriding-osgi-services}
Components register as services with the OSGi service registry. A
service component's availability, ranking, and attributes determine
whether components referring to the service type bind to that particular
service. Liferay DXP's OSGI container is a dynamic environment in which
services come and go and can be overridden, which means that if there's
a service whose behavior you want to change, you can override it. Here
are the steps for overriding a service:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override}{Get
the service and service reference details}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service}{Create
a custom service}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/reconfiguring-components-to-use-your-service}{Configure
components to use your custom service}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} The
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} services in
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/}{portal-impl}
are Spring beans that Liferay makes available as OSGi services.
\noindent\hrulefill
Start with examining the service you want to override.
\chapter{Examining an OSGi Service to
Override}\label{examining-an-osgi-service-to-override}
Creating and injecting a custom service in place of an existing service
requires three things:
\begin{itemize}
\tightlist
\item
Understanding the service interface
\item
The existing service
\item
The references to the service
\end{itemize}
Your custom service must implement the service interface, match
references you want, and might need to invoke the existing service.
Getting components to adopt your custom service immediately can require
reconfiguring their references to the service. Here you'll flesh out
service details to make these decisions.
\section{Gathering Information on a
Service}\label{gathering-information-on-a-service}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Since component service references are extension points, start with
determining the service you want to override and components that use
that service.
\item
Once you know the service and components that use it, use Gogo Shell's
Service Component Runtime (SCR) to inspect the components and get the
service and reference details. The
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
Shell} command \texttt{scr:info\ {[}componentName{]}} lists the
component's attributes and service references.
\end{enumerate}
Here's an example \texttt{scr:info} command and results (abbreviated
with \texttt{...}) that describe component
\texttt{override.my.service.reference.OverrideMyServiceReference} (from
sample module
\href{https://portal.liferay.dev/documents/113763090/114000186/override-my-service-reference.zip}{override-my-service-reference})
and its reference to a service of type
\texttt{override.my.service.reference.service.api.SomeService}:
\begin{verbatim}
> scr:info override.my.service.reference.OverrideMyServiceReference
...
Component Description:
Name: override.my.service.reference.portlet.OverrideMyServiceReferencePortlet
...
Reference: _someService
Interface Name: override.my.service.reference.service.api.SomeService
Cardinality: 1..1
Policy: static
Policy option: reluctant
Reference Scope: bundle
...
Component Configuration:
ComponentId: 2399
State: active
SatisfiedReference: _someService
Target: null
Bound to: 6840
Properties:
component.id = 2400
component.name = override.my.service.reference.service.impl.SomeServiceImpl
objectClass = [override.my.service.reference.service.api.SomeService]
service.bundleid = 524
service.id = 6840
service.scope = bundle
...
\end{verbatim}
The \texttt{scr:info} results, like the ones above, contain information
relevant to injecting a custom service. Here's what you'll do with the
information:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\hyperref[step-1-copy-the-service-interface-name]{Copy the service
interface name}
\item
\hyperref[step-2-copy-the-existing-service-name]{Copy the existing
service name}
\item
\hyperref[step-3-gather-reference-configuration-details-if-reconfiguration-is-needed]{Gather
reference configuration details (if reconfiguration is necessary)}
\end{enumerate}
Start with the service interface.
\section{Step 1: Copy the Service Interface
Name}\label{step-1-copy-the-service-interface-name}
The reference's \emph{Interface Name} is the service interface's fully
qualified name.
\begin{verbatim}
...
Reference: _someService
Interface Name: override.my.service.reference.service.api.SomeService
...
\end{verbatim}
\textbf{Copy and save the interface name}, because it's the type your
custom service must implement.
\noindent\hrulefill
Javadocs for Liferay DXP service interfaces are at these locations:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/}{Liferay
DXP core Javadocs}
\item
\href{https://docs.liferay.com/dxp/apps}{Liferay DXP app Javadocs}
\item
\href{https://mvnrepository.com/}{MVNRepository} and
\href{https://search.maven.org/}{Maven Central} (for Liferay and
non-Liferay artifact Javadocs).
\end{itemize}
\noindent\hrulefill
\section{Step 2: Copy the Existing Service
Name}\label{step-2-copy-the-existing-service-name}
If you want to invoke the existing service along with your custom
service, get the existing service name.
The \texttt{src:info} result's Component Configuration section lists the
existing service's fully qualified name. For example, the
\texttt{OverrideMyServiceReferencePortlet} component's references
\texttt{\_someService} is bound to a service component whose fully
qualified name is
\texttt{override.my.service.reference.service.impl.SomeServiceImpl}.
\begin{verbatim}
Component Configuration:
...
SatisfiedReference: _someService
...
Bound to: 6840
Properties:
...
component.name = override.my.service.reference.service.impl.SomeServiceImpl
\end{verbatim}
\textbf{Copy the \texttt{component.name}} so you can reference the
service in your
\href{/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service}{custom
service}.
Here's an example of referencing the service above.
\begin{verbatim}
@Reference (
target = "(component.name=override.my.service.reference.service.impl.SomeServiceImpl)"
)
private SomeService _defaultService;
\end{verbatim}
\section{Step 3: Gather Reference Configuration Details (if
reconfiguration is
needed)}\label{step-3-gather-reference-configuration-details-if-reconfiguration-is-needed}
The service reference's policy and policy option determine a component's
conditions for adopting a particular service.
\begin{itemize}
\item
If the reference's policy option is \texttt{greedy}, it binds to the
matching, highest ranking service right away. The reference need not
be reconfigured to adopt your service.
\item
If policy is \texttt{static} and its policy option is
\texttt{reluctant}, however, the component requires one of the
following conditions to switch from using the existing service it's
referencing to using the matching, highest ranking service (i.e.,
you'll rank your custom service highest):
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
The component is reactivated
\item
The component's existing referenced service is unavailable
\item
The component's reference is modified so that it does not match the
existing service but matches your service
\end{enumerate}
\end{itemize}
\href{/docs/7-2/customization/-/knowledge_base/c/reconfiguring-components-to-use-your-service}{Reconfiguring
the reference} can be the quickest way for the component to adopt a new
service.
\textbf{Gather these details:}
\begin{itemize}
\item
\emph{Component name:} Find this at \emph{Component Description} →
\emph{Name}. For example,
\begin{verbatim}
Component Description:
Name: override.my.service.reference.portlet.OverrideMyServiceReferencePortlet
...
\end{verbatim}
\item
\emph{Reference name:} The \emph{Reference} value (e.g.,
\texttt{Reference:\ \_someService}).
\item
\emph{Cardinality:} Number of service instances the reference can bind
to.
\end{itemize}
\noindent\hrulefill
\textbf{Note}: Declarative Services makes all components configurable
through OSGi Configuration Admin. Each \texttt{@Reference} annotation in
the source code has a name property, either \emph{explicitly} set in the
annotation or \emph{implicitly} derived from the name of the member on
which the annotation is used.
\begin{itemize}
\tightlist
\item
If no reference name property is used and the \texttt{@Reference} is
on a field, then the reference name is the field name. If
\texttt{@Reference} is on a field called \texttt{\_someService}, for
example, then the reference name is \texttt{\_someService}.
\item
If the \texttt{@Reference} is on a method, then heuristics derive the
reference name. Method name suffix is used and prefixes such as
\texttt{set}, \texttt{add}, and \texttt{put} are ignored. If
\texttt{@Reference} is on a method called
\texttt{setSearchEngine(SearchEngine\ se)}, for example, then the
reference name is \texttt{SearchEngine}.
\end{itemize}
\noindent\hrulefill
After
\href{/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service}{creating
your custom service} (next), you'll use the details you collected here
to
\href{/docs/7-2/customization/-/knowledge_base/c/reconfiguring-components-to-use-your-service}{configure
the component to use your custom service}.
Congratulations on getting the details required for overriding the OSGi
service!
\section{Related Topics}\label{related-topics-31}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi
Services and Dependency Injection with Declarative Services}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
Shell}
\end{itemize}
\chapter{Creating a Custom OSGi
Service}\label{creating-a-custom-osgi-service}
It's time to implement your OSGi service. Make sure to
\href{/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override}{examine
the service and service reference details}, if you haven't done so
already. Here you'll create a custom service that implements the service
interface, declares it an OSGi service of that type, and makes it the
best match for binding with other components.
The example custom service \texttt{CustomServiceImpl} implements service
interface (from sample module
\href{https://portal.liferay.dev/documents/113763090/114000186/overriding-service-reference.zip}{\texttt{overriding-service-reference}})
\texttt{SomeService}, declares itself an OSGi service of the
\texttt{SomeService} service type, and even delegates work to the
existing service. Examine the example code below as you follow the steps
for creating your custom service:
\begin{verbatim}
@Component(
property = {
"service.ranking:Integer=100"
},
service = SomeService.class
)
public class CustomServiceImpl implements SomeService {
@Override
public String doSomething() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName());
sb.append(", which delegates to ");
sb.append(_defaultService.doSomething());
return sb.toString();
}
@Reference (
target = "(component.name=override.my.service.reference.service.impl.SomeServiceImpl)"
)
private SomeService _defaultService;
}
\end{verbatim}
Here are the steps to create a custom OSGi service:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a module}.
\item
Create your custom service class so that it \texttt{implements} the
\href{/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override\#step-1-copy-the-service-interface-name}{service
interface} you want. In the example above,
\texttt{CustomServiceImpl\ implements\ SomeService}. Step 5 (later)
demonstrates implementing the interface methods.
\item
Make your class a Declarative Services component that is the best
match for references to the service interface:
\begin{itemize}
\item
Use an \texttt{@Component} annotation and \texttt{service} attribute
to make your classes a Declarative Services (DS) component. This
declares your class to be an OSGi service that can be made available
in the OSGi service registry. The example class above is a DS
service component of service type \texttt{SomeService.class}.
\item
Use a \texttt{service.ranking:Integer} component property to rank
your service higher than existing services. The
\texttt{"service.ranking:Integer=100"} property above sets the
example's ranking to \texttt{100}.
\end{itemize}
\item
If you want to invoke the existing service implementation, declare a
field that uses a Declarative Services reference to the existing
service. Use the
\href{/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override\#step-2-copy-the-existing-service-name}{\texttt{component.name}
you copied when you examined the service} to target the existing
service. The example above refers to an existing service like this:
\begin{verbatim}
@Reference (
target = "(component.name=override.my.service.reference.service.impl.SomeServiceImpl)"
)
private SomeService _defaultService;
\end{verbatim}
The field lets you invoke the existing service in your custom service.
\item
Override the interface's methods. Optionally, delegate work to the
existing service implementation (see previous step).
The example custom service's \texttt{doSomething} method delegates
work to the original service implementation.
\item
Register your custom service with the OSGi runtime framework by
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploying
your module}.
\end{enumerate}
Components that reference the service type you implemented and whose
reference policy option is \texttt{greedy} bind to your custom service
immediately. Components bound to an existing service and whose reference
policy option is \texttt{reluctant} can be dynamically reconfigured to
use your service. That's demonstrated next.
\section{Related Topics}\label{related-topics-32}
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi
Services and Dependency Injection with Declarative Services}
\chapter{Reconfiguring Components to Use Your OSGi
Service}\label{reconfiguring-components-to-use-your-osgi-service}
In many cases, assigning your
\href{/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service}{custom
service (service)} a higher ranking convinces components to unbind from
their current service and bind to yours. In other cases, components keep
using their current service. Why is that? And how do you make components
adopt your service? The component's
\href{/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override\#step-3-gather-reference-configuration-details-if-reconfiguration-is-needed}{service
reference policy option} is the key to determining the service.
Here are the policy options:
\texttt{greedy}: The component uses the matching, highest ranking
service as soon as it's available.
\texttt{reluctant}: The component uses the matching, highest ranking
service available in the following events:
\begin{itemize}
\tightlist
\item
the component is (re)activated
\item
the component's existing referenced service becomes unavailable
\item
the component's reference is modified so that it no longer matches the
existing bound service
\end{itemize}
In short, references with greedy policy options adopt your higher
ranking service right away, while ones with reluctant policy options
require particular events. What's great is that Liferay DXP's
Configuration Admin lets you use configuration files (config files) or
the API to swap in service reference changes on the fly. Here you'll use
a config file to reconfigure a service reference to use your custom
service immediately.
This article uses example modules \texttt{override-my-service-reference}
and \texttt{overriding-service-reference} to demonstrate reconfiguring a
service reference, binding the component to a different service. you can
apply the steps below to configure your own customization.
\begin{itemize}
\item
\texttt{override-my-service-reference}
(\href{https://portal.liferay.dev/documents/113763090/114000186/override-my-service-reference.zip}{download}):
This module's portlet component
\texttt{OverrideMyServiceReferencePortlet}'s field
\texttt{\_someService} references a service of type
\texttt{SomeService}. The reference's policy is static and reluctant.
By default, it binds to an implementation called
\texttt{SomeServiceImpl}.
\item
\texttt{overriding-service-reference}
(\href{https://portal.liferay.dev/documents/113763090/114000186/overriding-service-reference.zip}{download}):
Provides a custom \texttt{SomeService} implementation called
\texttt{CustomServiceImpl}. The module's configuration file overrides
\texttt{OverrideMyServiceReferencePortlet}'s \texttt{SomeService}
reference so that it binds to \texttt{CustomServiceImpl}.
\end{itemize}
You're ready to reconfigure a component's service reference to target
your custom service.
\section{Reconfiguring the Service
Reference}\label{reconfiguring-the-service-reference}
Liferay DXP's Configuration Admin lets you use configuration files to
swap in service references on the fly.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{Create
a system configuration file} named after the referencing component.
Follow the name convention \texttt{{[}component{]}.config}, replacing
\texttt{{[}component{]}} with the
\href{/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override\#step-3-gather-reference-configuration-details-if-reconfiguration-is-needed}{component
name}. The configuration file name for the example component
\texttt{override.my.service.reference.portlet.OverrideMyServiceReferencePortlet}
is:
\begin{verbatim}
override.my.service.reference.portlet.OverrideMyServiceReferencePortlet.config
\end{verbatim}
\item
In the configuration file, add a reference target entry that filters
on your custom service. Follow this format for the entry:
\begin{verbatim}
[reference].target=[filter]
\end{verbatim}
Replace \texttt{{[}reference{]}} with the name of the reference you're
overriding. Replace \texttt{{[}filter{]}} with service properties that
filter on your custom service.
This example filters on the \texttt{component.name} service property:
\begin{verbatim}
_someService.target="(component.name\=overriding.service.reference.service.CustomServiceImpl)"
\end{verbatim}
This example filters on the \texttt{service.vendor} service property:
\begin{verbatim}
_someService.target="(service.vendor\=Acme, Inc.)"
\end{verbatim}
\item
Optionally, you can add a \texttt{cardinality.minimum} entry to
specify the number of services the reference can use. Here's the
format:
\begin{verbatim}
[reference].cardinality.minimum=[int]
\end{verbatim}
Here's an example cardinality minimum:
\begin{verbatim}
_someService.cardinality.minimum=1
\end{verbatim}
\item
Deploy the configuration by copying the configuration file into the
folder \texttt{{[}Liferay\_Home{]}/osgi/configs}.
\end{enumerate}
Executing \texttt{scr:info} on your component shows that the custom
service is now bound to the reference.
For example, executing
\texttt{scr:info\ override.my.service.reference.portlet.OverrideMyServiceReferencePortlet}
reports the following information:
\begin{verbatim}
...
Component Description:
Name: override.my.service.reference.portlet.OverrideMyServiceReferencePortlet
...
Reference: _someService
Interface Name: override.my.service.reference.service.api.SomeService
Cardinality: 1..1
Policy: static
Policy option: reluctant
Reference Scope: bundle
...
Component Configuration:
ComponentId: 2399
State: active
SatisfiedReference: _someService
Target: (component.name=overriding.service.reference.CustomServiceImpl)
Bound to: 6841
Properties:
_defaultService.target = (component.name=overriding.service.reference.service.CustomServiceImpl)
component.id = 2398
component.name = overriding.service.reference.service.CustomServiceImpl
objectClass = [override.my.service.reference.service.api.SomeService]
service.bundleid = 525
service.id = 6841
service.scope = bundle
Component Configuration Properties:
_someService.target = (component.name=overriding.service.reference.service.CustomServiceImpl)
...
\end{verbatim}
The example component's \texttt{\_someService} reference targets the
custom service component
\texttt{overriding.service.reference.service.CustomServiceImpl}.
\texttt{CustomServiceImpl} references default service
\texttt{SomeServiceImpl} to delegate work to it.
\begin{figure}
\centering
\includegraphics{./images/overriding-service-refs-result.png}
\caption{Because the example component's service reference is overridden
by the configuration file deployment, the portlet indicates it's calling
the custom service.}
\end{figure}
Liferay DXP processed the configuration file and injected the service
reference, which in turn bound the custom service to the referencing
component!
\section{Related Topics}\label{related-topics-33}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi
Services and Dependency Injection with Declarative Services}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Using
Felix Gogo Shell}
\end{itemize}
\chapter{Portlet Filters}\label{portlet-filters}
Portlet filters intercept requests and responses at the start of the
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{portlet request
processing phase}. Portlet filters are commonly used for these things:
\begin{itemize}
\tightlist
\item
Transform content
\item
Add or modify request and response attributes
\item
Suspend a portlet phase to get user input
\item
Audit portlet activity
\end{itemize}
The
\href{http://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/filter/package-frame.html}{\texttt{javax.portlet.filter}}
package defines a portlet filter interface for each phase. Here are the
steps for developing a portlet filter:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Implement the
\href{http://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/filter/package-frame.html}{portlet
filter interface} for the phase it's intercepting. Here are common
interface methods to override:
\texttt{doFilter}: Here's where you take action. This method is
invoked at the start of the portlet request processing phase. The
request and response parameters provide access to portlet content and
attributes. The \texttt{FilterChain} parameter can be used to invoke
the next filter in the phase.
\texttt{init}: Initialize the filter. The \texttt{FilterConfig}
parameter can be used to prepare the filter.
\texttt{destroy}: Perform any filter cleanup.
\item
Target the desired portlet(s).
\item
Choose how to prioritize the filter among other filters in the phase:
\begin{itemize}
\tightlist
\item
OSGi Declarative Service Component portlet filters use a service
ranking property. High ranking filters execute before lower ones.
\item
\texttt{\textless{}filter-mapping\textgreater{}} element order in a
portlet application's \texttt{portlet.xml} file.
\item
The \texttt{ordinal} element value of a filter class annotated with
\texttt{@PortletLifecycleFilter}. Low ordinal value filters execute
before higher ones.
\end{itemize}
\end{enumerate}
Below is demonstrated applying multiple filters to a portlet's render
phase. The filters are
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi
Declarative Service (DS) Components}, but filters can also be applied to
a portlet using a \texttt{portlet.xml} descriptor or a
\texttt{@PortletLifecycleFilter} annotation. See the Portlet 3.0
Specification for details. The sample code is available
\href{https://portal.liferay.dev/learn/code-samples/-/cs/list/7.2/java8/workspace-gradle/modules/applications/portlets/render-filter-portlet}{here}.
\section{Sample Portlet}\label{sample-portlet}
The sample portlet \texttt{MembersListPortlet} is a
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC Portlet} that lists names and email addresses when users click its
\emph{Load Users} button. The information is based on \texttt{Person}
objects that the portlet class passes to the View template via a request
attribute called \texttt{MembersListPortlet.MEMBERLIST\_ATTRIBUTE}.
\begin{verbatim}
public void loadUsers(ActionRequest actionRequest, ActionResponse actionResponse) {
actionRequest.setAttribute(MembersListPortlet.MEMBERLIST_ATTRIBUTE, createStaticUserList());
}
\end{verbatim}
Two render filters are applied to the portlet:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Render filter 1 hides parts of the user email addresses (e.g., for
privacy) by modifying the request object.
\item
Render filter 2 logs portlet render phase statistics.
\end{enumerate}
Adding the \texttt{MemberList} portlet to a page and clicking the
\texttt{Load\ Users} button renders each \texttt{Person}'s name and
partially hidden email address, thanks to the filter
\texttt{EncodingPersonEmailsRenderFilter}.
\begin{verbatim}
Sievert Shayne
Sievert.Sha...@...mple.com
Vida Jonas
Vida.Jo...@...mple.com
...
\end{verbatim}
If you set the portlet's log level to \texttt{debug}, it prints the
render phase statistics.
\begin{verbatim}
Portlet com_liferay_code_samples_portal_modules_applications_portlets_render_filter_MembersListPortlet rendered in 7791 ms
Portlet com_liferay_code_samples_portal_modules_applications_portlets_render_filter_MembersListPortlet rendered 2 times with an average 356135 ms render time
\end{verbatim}
The first filter modifies portlet content via the request object.
\section{Render filter 1 hides parts of user email
addresses}\label{render-filter-1-hides-parts-of-user-email-addresses}
\texttt{EncodingPersonEmailsRenderFilter} is a \texttt{RenderFilter}
that hides parts of user email addresses by modifying a request
attribute. Here is the class:
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + MembersListPortlet.MEMBERSLIST_PORTLET_NAME,
"service.ranking:Integer=1"
},
service = PortletFilter.class
)
public class EncodingPersonEmailsRenderFilter implements RenderFilter {
@Override
public void doFilter(RenderRequest request, RenderResponse response, FilterChain chain)
throws IOException, PortletException {
//This is executed before the portlet render
Optional.ofNullable((List)request.getAttribute(MembersListPortlet.MEMBERLIST_ATTRIBUTE))
.ifPresent(personList ->
request.setAttribute(MembersListPortlet.MEMBERLIST_ATTRIBUTE, ofuscateEmails(personList)));
// Invoke the rest of the filters in the chain
// (it also invokes the Portlet render method if this is the last filter in the chain
chain.doFilter(request, response);
}
private List ofuscateEmails(List list) {
return list.stream()
.map(this::ofuscatePersonEmail)
.collect(Collectors.toList());
}
private Person ofuscatePersonEmail(Person person) {
return new Person(person.getName(),
person.getEmail().replaceFirst("(.+)(...)@(...)(.*)", "$1...@...$4"));
}
@Override
public void init(FilterConfig filterConfig) throws PortletException {
}
@Override
public void destroy() {
}
}
\end{verbatim}
The \texttt{@Component} annotation declares the filter to be an OSGi DS
Component. Here are its elements and properties:
\texttt{immediate\ =\ true} sets the component ready to start upon being
installed.
\texttt{service\ =\ PortletFilter.class} defines the component to be a
\texttt{PortletFilter} service.
\texttt{javax.portlet.name\ =\ +\ MembersListPortlet.MEMBERSLIST\_PORTLET\_NAME}
links the filter to the target portlet. Note, multiple portlets can be
listed.
\texttt{service.ranking:Integer=1} sets the filter to execute after
filters that are ranked higher than \texttt{1}.
\texttt{EncodingPersonEmailsRenderFilter} \emph{implements} the
\href{http://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/filter/RenderFilter.html}{\texttt{RenderFilter}}
interface, overriding the \texttt{doFilter}, \texttt{init}, and
\texttt{destroy} methods.
\texttt{doFilter} modifies the attribute
\texttt{MembersListPortlet.MEMBERLIST\_ATTRIBUTE}'s list of
\texttt{Person}s by replacing parts of their email addresses with
ellipses (\texttt{...}). It delegates the \texttt{ofuscatePersonEmail}
method to do the modifications. Then \texttt{doFilter} invokes
\texttt{chain.doFilter(request,\ response)} to execute the next
\texttt{RenderFilter} or next portlet processing phase.
\noindent\hrulefill
\textbf{Note:} Filters can also intercept and block the execution of a
portlet phase. In the \texttt{doFilter} method, this is usually done by
throwing an exception or by not calling the next element in the filter
chain.
\noindent\hrulefill
\section{RenderFilter 2 Logs
Statistics}\label{renderfilter-2-logs-statistics}
\texttt{MembersListStatsRenderFilter} is a \texttt{RenderFilter} that
logs the number of times the portlet is rendered and the average render
time. Here's the code:
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + MembersListPortlet.MEMBERSLIST_PORTLET_NAME,
"service.ranking:Integer=100"
},
service = PortletFilter.class
)
public class MembersListStatsRenderFilter implements RenderFilter {
//Thread safe - accumulator that keeps the number of times the portlet has been rendered
private final LongAdder hits = new LongAdder();
//Thread safe accumulator that keeps total time spent rendering the portlet.
private final LongAdder accumulatedTimeMs = new LongAdder();
@Override
public void doFilter(RenderRequest request, RenderResponse response, FilterChain chain) throws IOException, PortletException {
long startTime = System.nanoTime();
chain.doFilter(request, response);
long renderTime = (System.nanoTime() - startTime) / 1000;
hits.increment();
accumulatedTimeMs.add(renderTime);
if (LOG.isDebugEnabled()) {
long totalHits = hits.longValue();
long averageRenderTimeNs = accumulatedTimeMs.longValue() / totalHits;
LOG.debug("Portlet " + MembersListPortlet.MEMBERSLIST_PORTLET_NAME + " rendered in " + renderTime + " ms");
LOG.debug("Portlet " + MembersListPortlet.MEMBERSLIST_PORTLET_NAME + " rendered " + hits.longValue()
+ " times with an average " + averageRenderTimeNs + " ms render time");
}
}
...
private static final Log LOG = LogFactoryUtil.getLog(MembersListStatsRenderFilter.class);
}
\end{verbatim}
As with \texttt{EncodingPersonEmailsRenderFilter}, it's an OSGi DS
Component that is a \texttt{PortletFilter} service, starts upon
installation, applies to the \texttt{MembersListPortlet}, and has a
service ranking. Since its ranking is \texttt{100}, it is executed
before render filter \texttt{EncodingPersonEmailsRenderFilter}.
\texttt{MembersListStatsRenderFilter}'s \texttt{doFilter()} method
audits the render phase in these ways:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Notes the render phase start time.
\item
Executes \texttt{chain.doFilter(request,\ response)} to invoke all of
the other \texttt{RenderFilter}s in the \texttt{FilterChain}.
\item
Increments the number of times the portlet renders.
\item
Calculates the average render time.
\item
Logs the times rendered and average render time.
\end{enumerate}
Consider creating your own filters to intercept portlet processing
phases.
\section{Related Topics}\label{related-topics-34}
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters}{JSP
Overrides Using Portlet Filters}
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC Portlet}
\chapter{Product Navigation}\label{product-navigation}
Liferay DXP's product navigation consists of the main menus you use to
customize, configure, and navigate the system. When you edit a page,
switch to a different Site scope, access a User's credentials, etc.,
you're using the default navigation menus. Customizing a default menu
can help give your Liferay instance a unique touch. You can extend and
customize the default product navigation to fit your need.
There are four product navigation sections that you can extend:
\begin{itemize}
\tightlist
\item
Product Menu
\item
Control Menu
\item
Simulation Menu
\item
User Personal Menu
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/product-navigation-summary.png}
\caption{The main product navigation menus include the Product Menu,
Control Menu, Simulation Menu and User Personal Menu.}
\end{figure}
The Product Menu is on the left, and displays the Control Panel and Site
Administration functionality. The Control Menu is on top, offering
navigation to the Product Menu, Simulation Menu (the right menu), and
the \emph{Add} button. When certain settings are enabled (e.g., Staging,
Page Customization, etc.), more tools are offered. The Simulation Menu
offers options to simulate your Site's look for different scenarios
(devices, user segments, etc.). Finally, the User Personal Menu holds
selectable items containing a user's own account settings.
You'll learn more about each of these product navigation sections next.
\section{Product Menu}\label{product-menu}
By default, Liferay's Product Menu consists of two main sections:
Control Panel and Site Administration. These sections are called
\emph{Panel Categories}. For instance, the Control Panel is a single
Panel Category, and when clicking on it, you see six child Panel
Categories: \emph{Users}, \emph{Sites}, \emph{Apps},
\emph{Configuration}, and \emph{Workflow}. Clicking a child Panel
Category shows \emph{panel apps}.
The Product Menu is intuitive and easy to use---but you can still change
it any way you want. You can reorganize the Panel Categories and apps,
or add completely new categories and populate them with custom Panel
Apps. You'll learn how to provide new or modified Panel Categories and
Panel Apps for the Product Menu. For more information, read the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu}{Customizing
the Product Menu} articles.
\section{Control Menu}\label{control-menu}
The Control Menu is the most visible and accessible menu. For example,
on your home page, the Control Menu offers default options for accessing
the Product Menu, Simulation Menu, and Add Menu. You can think of this
menu as the gateway to configuring options in Liferay DXP.
\begin{figure}
\centering
\includegraphics{./images/control-menu-home.png}
\caption{The Control Menu has three configurable areas: left, right, and
middle. It also displays the title and type of page that you are
currently viewing.}
\end{figure}
If you navigate away from the home page, the Control Menu adapts and
provides helpful functionality for whatever option you're using. For
example, if you navigate to Site Administration → \emph{Content \& Data}
→ \emph{Web Content}, you see a Control Menu with different
functionality tailored for that option.
\begin{figure}
\centering
\includegraphics{./images/control-menu-web-content.png}
\caption{When switching your context to web content, the Control Menu
adapts to provide helpful options for that area.}
\end{figure}
The default Control Menu contains three categories representing the
left, middle, and right portions of the menu. You can create navigation
entries for each category. For more information, read the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu}{Customizing
the Control Menu} articles.
\section{Simulation Menu}\label{simulation-menu}
When testing how pages and apps appear for users, it's important to
simulate their views in as many ways as possible. The Simulation Menu on
the right-side of the main page allows this, and you can extend the menu
if you need to simulate something that it does not provide.
\begin{figure}
\centering
\includegraphics{./images/simulation-menu-preview.png}
\caption{The Simulation Menu offers a device preview application.}
\end{figure}
There are few differences between the Simulation Menu and Product Menu,
mostly because they extend the same base classes. The Simulation Menu,
by default, is made up of only one Panel Category and one Panel App.
Liferay provides the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/product-navigation/product-navigation-simulation-web/src/main/java/com/liferay/product/navigation/simulation/web/internal/application/list/SimulationPanelCategory.java}{\texttt{SimulationPanelCategory}}
class, a hidden category needed to hold the
\texttt{DevicePreviewPanelApp}. This is the app and functionality you
see in the Simulation Menu by default.
For more information, read the
\href{/docs/7-2/customization/-/knowledge_base/c/extending-the-simulation-menu}{Extending
the Simulation Menu} article.
\section{User Personal Menu}\label{user-personal-menu}
The User Personal Menu displays options unique to the current user. By
default, this menu appears as an avatar button that expands the User
Settings sub-menu just below the Control Menu. In a custom theme, the
User Personal Menu could appear anywhere in the interface.
\begin{figure}
\centering
\includegraphics{./images/user-personal-menu.png}
\caption{By default, the User Personal Menu contains the signed-in
user's avatar, which opens the user's settings when selected.}
\end{figure}
Although Liferay's default User Personal Menu is bare-bones, you can add
more functionality to fit your needs. Unlike other product navigation
menus (e.g., Product Menu), the User Personal Bar does not require the
extension/creation of Panel Categories and Panel Apps. It uses another
common Liferay framework for providing functionality:
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes}{Portlet
Providers}.
The User Personal Menu can be seen as a placeholder in every Liferay
theme. By default, Liferay provides one sample \emph{User Personal Bar}
portlet that fills that placeholder, but the portlet Liferay provides
can be replaced by other portlets.
\noindent\hrulefill
\textbf{Note:} You can add the User Personal Bar to your theme by adding
the following snippet into your \texttt{portal\_normal.ftl}:
\begin{verbatim}
<@liferay.user_personal_bar />
\end{verbatim}
\noindent\hrulefill
For more information, read the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-user-personal-bar-and-menu}{Customizing
the User Personal Bar and Menu} article.
\chapter{Customizing the Product
Menu}\label{customizing-the-product-menu}
Customizing the Product Menu can be completed by adding Panel Categories
and Panel Apps.
\noindent\hrulefill
\textbf{Note:} The Product Menu cannot be changed by applying a new
theme. To change the layout/style of the Product Menu, you must create
and deploy a theme contributor. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site}{Theme
Contributors} article for more details.
\noindent\hrulefill
To create these entities, you must implement the
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/PanelCategory.html}{\texttt{PanelCategory}}
and
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/PanelApp.html}{\texttt{PanelApp}}
interfaces.
\section{PanelCategory Interface}\label{panelcategory-interface}
The \texttt{PanelCategory} interface requires you to implement the
following methods:
\begin{itemize}
\tightlist
\item
\texttt{getNotificationCount}: returns the number of notifications to
be shown in the Panel Category.
\item
\texttt{include}: renders the body of the Panel Category.
\item
\texttt{includeHeader}: renders the Panel Category header.
\item
\texttt{isActive}: whether the panel is selected.
\item
\texttt{isPersistState}: whether to persist the Panel Category's state
to the database. This saves the state of the Panel Category when
navigating away from the menu.
\end{itemize}
You can reduce the number of methods you must implement if you extend a
base class that already implements the \texttt{PanelCategory} interface.
The recommended way to do this is by extending the
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/BasePanelCategory.html}{\texttt{BasePanelCategory}}
or
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelCategory.html}{\texttt{BaseJSPPanelCategory}}
abstract classes. Typically, the \texttt{BasePanelCategory} is extended
for basic categories (e.g., the Control Panel category) that only
display the category name. To add more complex functionality, you can
then provide a custom UI for your panel using any front-end technology
by implementing the \texttt{include()} or \texttt{includeHeader()} from
the \texttt{PanelCategory} interface.
If you plan to use JSPs as the front-end technology, extend a base class
called \texttt{BaseJSPPanelCategory} that already implements the methods
\texttt{include()} and \texttt{includeHeader()} for you.
\noindent\hrulefill
\textbf{Note:} In this article, example JSPs describe how to provide
functionality to Panel Categories and Panel Apps. JSPs, however, are not
the only way to provide front-end functionality to your categories/apps.
You can create your own class implementing \texttt{PanelCategory} to use
other technologies such as FreeMarker.
\noindent\hrulefill
More information on provided base classes for your
\texttt{PanelCategory} implementation are described next.
\section{BasePanelCategory}\label{basepanelcategory}
If you need something simple for your Panel Category like a name,
extending \texttt{BasePanelCategory} is probably sufficient. For
example, the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/product-navigation/product-navigation-control-panel/src/main/java/com/liferay/product/navigation/control/panel/internal/application/list/ControlPanelCategory.java}{\texttt{ControlPanelCategory}}
extends \texttt{BasePanelCategory} and specifies a \texttt{getLabel}
method to set and display the Panel Category name.
\begin{verbatim}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "control-panel");
}
\end{verbatim}
\section{BaseJSPPanelCategory}\label{basejsppanelcategory}
If you need more complex functionality, extend
\texttt{BaseJSPPanelCategory} and use JSPs to render the Panel Category.
For example, the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/product-navigation/product-navigation-site-administration/src/main/java/com/liferay/product/navigation/site/administration/internal/application/list/SiteAdministrationPanelCategory.java}{\texttt{SiteAdministrationPanelCategory}}
specifies the \texttt{getHeaderJspPath} and \texttt{getJspPath} methods.
You could create a JSP with the UI you want to render and specify its
path in methods like these:
\begin{verbatim}
@Override
public String getHeaderJspPath() {
return "/sites/site_administration_header.jsp";
}
@Override
public String getJspPath() {
return "/sites/site_administration_body.jsp";
}
\end{verbatim}
One JSP renders the Panel Category's header (displayed when panel is
collapsed) and the other its body (displayed when panel is expanded).
Next, you'll learn about the \texttt{PanelApp} interface.
\section{PanelApp Interface}\label{panelapp-interface}
The \texttt{PanelApp} interface requires you to implement the following
methods:
\begin{itemize}
\tightlist
\item
\texttt{getNotificationCount}: returns the number of notifications for
the user.
\item
\texttt{getPortlet}: returns the portlet associated with the
application.
\item
\texttt{getPortletId}: returns the portlet's ID associated with the
application.
\item
\texttt{getPortletURL}: returns the URL used to render a portlet based
on the servlet request attributes.
\item
\texttt{include}: Returns \texttt{true} if the application
successfully renders.
\item
\texttt{setGroupProvider}: sets the group provider associated with the
application.
\item
\texttt{setPortlet}: sets the portlet associated with the application.
\end{itemize}
You can reduce the number of methods you must implement if you extend a
base class that already implements the \texttt{PanelCategory} interface.
The recommended way to do this is by extending the
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/BasePanelApp.html}{\texttt{BasePanelApp}}
or
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelApp.html}{\texttt{BaseJSPPanelApp}}
abstract classes. If you want to use JSPs to render that UI, extend
\texttt{BaseJSPPanelApp}. This provides additional methods you can use
to incorporate JSP functionality into your app's listing in the Product
Menu.
\noindent\hrulefill
\textbf{Note:} JSPs are not the only way to provide front-end
functionality to your Panel Apps. You can create your own class
implementing \texttt{PanelApp} to use other technologies such as
FreeMarker.
\noindent\hrulefill
The \texttt{BlogsPanelApp} is a simple example of how to specify your
portlet as a Panel App. This class extends \texttt{BasePanelApp},
overriding the \texttt{getPortletId} and \texttt{setPortlet} methods.
These methods specify and set the Blogs portlet as a Panel App.
This is how those methods look for the Blogs portlet:
\begin{verbatim}
@Override
public String getPortletId() {
return BlogsPortletKeys.BLOGS_ADMIN;
}
@Override
@Reference(
target = "(javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN + ")",
unbind = "-"
)
public void setPortlet(Portlet portlet) {
super.setPortlet(portlet);
}
\end{verbatim}
Each Panel App must belong to a portlet and each portlet can have at
most one Panel App. If more than one Panel App is needed, another
portlet must be created. By default, the Panel App only appears if the
user has permission to view the associated portlet.
Continue on the learn about creating custom Panel Categories and Panel
Apps.
\chapter{Adding Custom Panel
Categories}\label{adding-custom-panel-categories}
As you navigate the Product Menu, you can see that Panel Apps like
\emph{Web Content} and \emph{Settings} are organized into Panel
Categories such as \emph{Content \& Data} and \emph{Configuration}. This
article explains how to add new Panel Categories to the menu. Adding new
Panel Apps is covered in the next section.
There are three steps to creating a new category:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create the OSGi structure and metadata.
\item
Implement Liferay's Frameworks.
\item
Define the Panel Category.
\end{enumerate}
\section{Creating the OSGi Module}\label{creating-the-osgi-module}
First you must create the project.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an OSGi module using your favorite third party tool, or use
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}.
Blade CLI offers a
\href{/docs/7-2/reference/-/knowledge_base/r/panel-app-template}{Panel
App} template, which is for creating a Panel Category and Panel App.
\item
Create a unique package name in the module's \texttt{src} directory
and create a new Java class in that package. To follow naming
conventions, give your class a unique name followed by
\texttt{PanelCategory} (e.g., \texttt{ControlPanelCategory}).
\end{enumerate}
\section{Implementing Liferay's
Frameworks}\label{implementing-liferays-frameworks}
Next, you must connect your OSGi module to Liferay's frameworks and use
those to define information about your entry. This takes only two steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Insert the \texttt{@Component} annotation declaring the panel category
keys directly above the class's declaration:
\begin{verbatim}
@Component(
immediate = true,
property = {
"panel.category.key=" + [Panel Category Key],
"panel.category.order:Integer=[int]"
},
service = PanelCategory.class
)
\end{verbatim}
You can view an example of a similar \texttt{@Component} annotation
for the \texttt{UserPanelCategory} class below:
\begin{verbatim}
@Component(
immediate = true,
property = {
"panel.category.key=" + PanelCategoryKeys.ROOT,
"panel.category.order:Integer=200"
},
service = PanelCategory.class
)
\end{verbatim}
The \texttt{property} element designates two properties that should be
assigned for your category. The \texttt{panel.category.key} specifies
the parent category for your custom category. You can find popular
parent categories to assign in the
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/PanelCategoryKeys.html}{\texttt{PanelCategoryKeys}}
class. For instance, if you wanted to create a child category in the
Control Panel, you could assign
\texttt{PanelCategoryKeys.CONTROL\_PANEL}. Likewise, if you wanted to
create a root category, like the Control Panel or Site Administration,
you could assign \texttt{PanelCategoryKeys.ROOT}.
The \texttt{panel.category.order:Integer} property specifies the order
in which your category is displayed. The higher the number (integer),
the lower your category is listed among other sibling categories
assigned to a parent.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** To insert a Panel Category between existing categories in the
default menu, you must know the `panel.category.order:Integer` property
for the existing categories. For example, the Product Menu's two main
sections---Control Panel and Site Administration---have
`panel.category.order:Integer` properties of 100 and 200, respectively. A
new panel inserted between Control Panel and Site Administration would
need a `panel.category.key` of ROOT and a `panel.category.order:Integer`
of 150.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
Finally, your `service` element should specify the `PanelCategory.class`
service.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Implement the \texttt{PanelCategory} interface. See the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu\#panelcategory-interface}{\texttt{PanelCategory}
Interface} section for more details. Extending one of the provided
base classes
(\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu\#basepanelcategory}{BasePanelCategory}
or
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu\#basejsppanelcategory}{BaseJSPPanelCategory})
is a popular way to implement the \texttt{PanelCategory} interface.
\item
If you elect to leverage JSPs, you must also specify the servlet
context from where you are loading the JSP files. If this is inside an
OSGi module, make sure your \texttt{bnd.bnd} file has defined a web
context path:
\begin{verbatim}
Bundle-SymbolicName: com.sample.my.module.web
Web-ContextPath: /my-module-web
\end{verbatim}
Then reference the Servlet context using the symbolic name of your
module like this:
\begin{verbatim}
@Override
@Reference(
target = "(osgi.web.symbolicname=com.sample.my.module.web)",
unbind = "-"
)
public void setServletContext(ServletContext servletContext) {
super.setServletContext(servletContext);
}
\end{verbatim}
\end{enumerate}
Excellent! You've successfully created a custom Panel Category to
display in the Product Menu. In many cases, a Panel Category holds Panel
Apps for users to access. You'll learn how to add a Panel App to a Panel
Category next.
\chapter{Adding Custom Panel Apps}\label{adding-custom-panel-apps}
After you have created a Panel Category, create a Panel App to go in it:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an OSGi module using your favorite third party tool, or use
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}.
Blade CLI offers a
\href{/docs/7-2/reference/-/knowledge_base/r/panel-app-template}{Panel
App} template to help generate a basic Panel Category and Panel App.
\item
Create a unique package name in the module's \texttt{src} directory,
and create a new Java class in that package. To follow naming
conventions, give your class a unique name followed by \emph{PanelApp}
(e.g., \texttt{JournalPanelApp}).
\item
Directly above the class's declaration, insert the following
annotation:
\begin{verbatim}
@Component(
immediate = true,
property = {
"panel.app.order:Integer=INTEGER"
"panel.category.key=" + PANEL_CATEGORY_KEY,
},
service = PanelApp.class
)
\end{verbatim}
You can view an example of a similar \texttt{@Component} annotation
for the \texttt{JournalPanelApp} class below.
\begin{verbatim}
@Component(
immediate = true,
property = {
"panel.app.order:Integer=100",
"panel.category.key=" + PanelCategoryKeys.SITE_ADMINISTRATION_CONTENT
},
service = PanelApp.class
)
\end{verbatim}
These properties and attributes are similar to those discussed in the
previous
\href{/docs/7-2/customization/-/knowledge_base/c/adding-custom-panel-categories}{article}.
The \texttt{panel.category.key} assigns your Panel App to a Panel
Category. The \texttt{panel.app.order:Integer} property specifies the
order your Panel App appears among other Panel Apps in the same
category. For example, if you want to add a Panel App to Site
Administration → \emph{Content \& Data}, add the following property:
\begin{verbatim}
"panel.category.key=" + PanelCategoryKeys.SITE_ADMINISTRATION_CONTENT
\end{verbatim}
Visit the
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/constants/PanelCategoryKeys.html}{PanelCategoryKeys}
class for keys you can use to specify default Panel Categories in
Liferay.
Set the \texttt{service} attribute to \texttt{PanelApp.class}.
\item
Implement the \texttt{PanelApp} interface. See the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu\#panelapp-interface}{\texttt{PanelApp}
Interface} section for more details. Extending one of the provided
base classes
(\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/BasePanelApp.html}{BasePanelApp}
or
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelApp.html}{BaseJSPPanelApp})
is a popular way to implement the \texttt{PanelApp} interface. See the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu\#panelapp-interface}{PanelApp
Interface} section for more information.
\item
If you elect to leverage JSPs, you must also specify the servlet
context from where you are loading the JSP files. If this is inside an
OSGi module, make sure your \texttt{bnd.bnd} file has defined a web
context path:
\begin{verbatim}
Bundle-SymbolicName: com.sample.my.module.web
Web-ContextPath: /my-module-web
\end{verbatim}
Then reference the Servlet context using the symbolic name of your
module like this:
\begin{verbatim}
@Override
@Reference(
target = "(osgi.web.symbolicname=com.sample.my.module.web)",
unbind = "-"
)
public void setServletContext(ServletContext servletContext) {
super.setServletContext(servletContext);
}
\end{verbatim}
\end{enumerate}
Now you know how to add or modify a Panel App in the Product Menu. Not
only does Liferay provide a simple solution to add new Panel Categories
and Panel Apps, it also gives you the flexibility to add a more complex
UI to the Product Menu using any technology.
\chapter{Customizing the Control
Menu}\label{customizing-the-control-menu}
Liferay's Control Menu consists of three main sections: Sites (left
portion), Tools (middle portion), and User (right portion).
\begin{figure}
\centering
\includegraphics{./images/control-menu-areas.png}
\caption{This image shows where your entry will reside depending on the
category you select.}
\end{figure}
\noindent\hrulefill
\textbf{Note:} You can add the Control Menu to a theme by adding the
following snippet into your \texttt{portal\_normal.ftl}:
\begin{verbatim}
<@liferay.control_menu />
\end{verbatim}
The other product navigation menus (e.g., Product Menu, Simulation Menu)
are included in this tag, so specifying the above snippet embeds all
three menus into your theme. Embedding the User Personal Menu is
slightly different. Visit the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-user-personal-bar-and-menu}{Customizing
the User Personal Bar and Menu} article for more information.
\noindent\hrulefill
You can reference a sample Control Menu Entry by visiting the
\href{/docs/7-2/reference/-/knowledge_base/r/control-menu-entry-template}{Control
Menu Entry} article.
\section{ProductNavigationControlMenuEntry
Interface}\label{productnavigationcontrolmenuentry-interface}
To create a control menu entry, you must implement the
\href{https://docs.liferay.com/dxp/apps/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/ProductNavigationControlMenuEntry.html}{\texttt{ProductNavigationControlMenuEntry}}
interface. It's recommended to implement this interface by extending the
\href{https://docs.liferay.com/dxp/apps/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseProductNavigationControlMenuEntry.html}{\texttt{BaseProductNavigationControlMenuEntry}}
or
\href{https://docs.liferay.com/dxp/apps/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseJSPProductNavigationControlMenuEntry.html}{\texttt{BaseJSPProductNavigationControlMenuEntry}}
abstract classes.
These base classes are covered in more detail next.
\section{BaseProductNavigationControlMenuEntry}\label{baseproductnavigationcontrolmenuentry}
Typically, the \texttt{BaseProductNavigationControlMenuEntry} is
extended for basic entries that only display a link with text or a
simple icon. If you want to provide a more complex UI with buttons or a
sub-menu, you can override the \texttt{include()} and
\texttt{includeBody()} methods.
The
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-web/src/main/java/com/liferay/portal/search/web/internal/product/navigation/control/menu/IndexingProductNavigationControlMenuEntry.java}{\texttt{IndexingProductNavigationControlMenuEntry}}
is a simple example for providing text and an icon. It extends the
\texttt{BaseProductNavigationControlMenuEntry} class and is used when
Liferay is indexing. The indexing entry is displayed in the \emph{Tools}
(middle) area of the Control Menu with a \emph{Refresh} icon and text
stating \emph{The Portal is currently indexing}.
\section{BaseJSPProductNavigationControlMenuEntry}\label{basejspproductnavigationcontrolmenuentry}
If you use JSPs for generating the UI, you can extend
\texttt{BaseJSPProductNavigationControlMenuEntry} to save time when
creating/modifying a control menu entry.
The
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/product-navigation/product-navigation-product-menu-web/src/main/java/com/liferay/product/navigation/product/menu/web/internal/product/navigation/control/menu/ProductMenuProductNavigationControlMenuEntry.java}{\texttt{ProductMenuProductNavigationControlMenuEntry}}
creates an entry that appears in the \emph{Sites} (left) area of the
Control Menu. This class extends the
\texttt{BaseJSPProductNavigationControlMenuEntry} class. This provides
several more methods that use JSPs to define your entry's UI. There are
two methods to notice:
\begin{verbatim}
@Override
public String getBodyJspPath() {
return "/portlet/control_menu/product_menu_control_menu_entry_body.jsp";
}
@Override
public String getIconJspPath() {
return "/portlet/control_menu/product_menu_control_menu_entry_icon.jsp";
}
\end{verbatim}
The \texttt{getIconJspPath()} method provides the Product Menu icon
(\includegraphics{./images/icon-menu.png} → !{[}Menu Open{]}(../../..and
the \texttt{getBodyJspPath()} method adds the UI body for the entry
outside of the Control Menu. The latter method must be used when
providing a UI outside the Control Menu. You can test this by opening
and closing the Product Menu on the home page.
Finally, if you provide functionality that is exclusively inside the
Control Menu, the \texttt{StagingProductNavigationControlMenuEntry}
class calls its JSP like this:
\begin{verbatim}
@Override
public String getIconJspPath() {
return "/control_menu/entry.jsp";
}
\end{verbatim}
The \texttt{entry.jsp} is returned, which embeds the Staging Bar portlet
into the Control Menu.
Next, you'll step through the process of customizing the Control Menu.
\chapter{Creating Control Menu
Entries}\label{creating-control-menu-entries}
Now you'll create entries to customize the Control Menu. Make sure to
read
\href{/docs/7-2/customization/-/knowledge_base/c/adding-custom-panel-categories}{Adding
Custom Panel Categories} before beginning this article. This article
assumes you know how to create a Panel Category. Creating a Control Menu
Entry follows the same pattern as creating a Panel Category:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create the OSGi structure and metadata.
\item
Implement Liferay's Frameworks.
\item
Define the Control Menu Entry.
\end{enumerate}
\section{Creating the OSGi Module}\label{creating-the-osgi-module-1}
First you must create the project.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a generic OSGi module. Your module must contain a Java class,
\texttt{bnd.bnd} file, and build file (e.g., \texttt{build.gradle} or
\texttt{pom.xml}). You'll create your Java class next if your project
does not already define one.
\item
Create a unique package name in the module's \texttt{src} directory
and create a new Java class in that package. Give your class a unique
name followed by \emph{ProductNavigationControlMenuEntry}
(e.g.,\texttt{StagingProductNavigationControlMenuEntry}).
\end{enumerate}
\section{Implementing Liferay's
Frameworks}\label{implementing-liferays-frameworks-1}
Next, you need to connect your OSGi module to Liferay's frameworks and
use those to define information about your entry.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Directly above the class's declaration, insert this code:
\begin{verbatim}
@Component(
immediate = true,
property = {
"product.navigation.control.menu.category.key=" + [Control Menu Category],
"product.navigation.control.menu.category.order:Integer=[int]"
},
service = ProductNavigationControlMenuEntry.class
)
\end{verbatim}
The \texttt{product.navigation.control.menu.category.key} property
specifies your entry's category. The default Control Menu provides
three categories: Sites (left portion), Tools (middle portion), and
User (right portion).
To specify the category, reference the appropriate key in the
\href{https://docs.liferay.com/dxp/apps/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/constants/ProductNavigationControlMenuCategoryKeys.html}{ProductNavigationControlMenuCategoryKeys}
class. For example, this property places your entry in the middle
portion of the Control Menu:
\begin{verbatim}
"product.navigation.control.menu.category.key=" + ProductNavigationControlMenuCategoryKeys.TOOLS
\end{verbatim}
Like Panel Categories, you must specify an integer to place your entry
in the category. Entries are ordered from left to right: an entry with
order \texttt{1} appears to the left of an entry with order
\texttt{2}. If the order is not specified, it's chosen at random based
on which service was registered first in the OSGi container.
Finally, your \texttt{service} element should specify the
\texttt{ProductNavigationControlMenuEntry.class} service.
\item
Implement the
\href{https://docs.liferay.com/dxp/apps/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/ProductNavigationControlMenuEntry.html}{\texttt{ProductNavigationControlMenuEntry}}
interface. You can also extend the
\href{https://docs.liferay.com/dxp/apps/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseProductNavigationControlMenuEntry.html}{\texttt{BaseProductNavigationControlMenuEntry}}
or
\href{https://docs.liferay.com/dxp/apps/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseJSPProductNavigationControlMenuEntry.html}{\texttt{BaseJSPProductNavigationControlMenuEntry}}
abstract classes. See the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu}{Customizing
the Control Menu} article for more information on these classes.
\item
If you elect to leverage JSPs, you must specify the servlet context
for the JSP files. If this is inside an OSGi module, make sure your
\texttt{bnd.bnd} file defines a web context path:
\begin{verbatim}
Bundle-SymbolicName: com.sample.my.module.web
Web-ContextPath: /my-module-web
\end{verbatim}
And then reference the Servlet context using the symbolic name of your
module:
\begin{verbatim}
@Override
@Reference(
target = "(osgi.web.symbolicname=com.sample.my.module.web)",
unbind = "-"
)
public void setServletContext(ServletContext servletContext) {
super.setServletContext(servletContext);
}
\end{verbatim}
\item
Part of creating the entry is defining when it appears. The Control
Menu shows different entries depending on the displayed page. You can
specify when your entry appears with the
\texttt{isShow(HttpServletRequest)} method.
For example, the \texttt{IndexingProductNavigationControlMenuEntry}
class queries the number of indexing jobs when calling
\texttt{isShow}. If the query count is \texttt{0}, the indexing entry
doesn't appear in the Control Menu:
\begin{verbatim}
@Override
public boolean isShow(HttpServletRequest request) throws PortalException {
int count = _indexWriterHelper.getReindexTaskCount(
CompanyConstants.SYSTEM, false);
if (count == 0) {
return false;
}
return super.isShow(request);
}
\end{verbatim}
The \texttt{StagingProductNavigationControlMenuEntry} class selects
the pages to appear. The staging entry never appears if the page is an
administration page (e.g., \emph{Site Administration}, \emph{Control
Panel}, etc.):
\begin{verbatim}
@Override
public boolean isShow(HttpServletRequest request) throws PortalException {
ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
WebKeys.THEME_DISPLAY);
Layout layout = themeDisplay.getLayout();
// This controls if the page is an Administration Page
if (layout.isTypeControlPanel()) {
return false;
}
// This controls if Staging is enabled
if (!themeDisplay.isShowStagingIcon()) {
return false;
}
return true;
}
\end{verbatim}
\end{enumerate}
Excellent! You've created your entry in one of the three default
sections in the Control Menu.
\chapter{Defining Icons and Tooltips}\label{defining-icons-and-tooltips}
When creating a Control Menu entry, you can use an icon in addition to
or in place of text. You can also use tooltips to provide a more in
depth explanation.
\section{Control Menu Entry Icons}\label{control-menu-entry-icons}
You can provide a Lexicon or CSS icon in your
\texttt{*ControlMenuEntry}. To use a Lexicon icon, you should override
the methods in \texttt{ProductMenuProductNavigationControlMenuEntry}
like this one:
\begin{verbatim}
public String getIconCssClass(HttpServletRequest request) {
return "";
}
public String getIcon(HttpServletRequest request) {
return "lexicon-icon";
}
public String getMarkupView(HttpServletRequest request) {
return "lexicon";
}
\end{verbatim}
Likewise, you can use a CSS icon by overriding the
\texttt{ProductMenuProductNavigationControlMenuEntry} methods like this
one:
\begin{verbatim}
public String getIconCssClass(HttpServletRequest request) {
return "icon-css";
}
public String getIcon(HttpServletRequest request) {
return "";
}
public String getMarkupView(HttpServletRequest request) {
return "";
}
\end{verbatim}
You can find these icons documented
\href{https://clayui.com/docs/components/icons.html}{here}.
\section{Control Menu Entry Tooltips}\label{control-menu-entry-tooltips}
To provide a tooltip for the Control Menu entry, create a
\texttt{getLabel} method like this:
\begin{verbatim}
@Override
public String getLabel(Locale locale) {
ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
"content.Language", locale, getClass());
return LanguageUtil.get(
resourceBundle, "the-portal-is-currently-reindexing");
}
\end{verbatim}
You need to create a \texttt{Language.properties} to store your labels.
You can learn more about resource bundles in the
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization}
articles.
\chapter{Extending the Simulation
Menu}\label{extending-the-simulation-menu}
To provide your own functionality in the Simulation Menu, you must
create a Panel App in \texttt{SimulationPanelCategory}. If you want to
add extensive functionality, you can even create additional Panel
Categories in the menu to divide up your Panel Apps. This article covers
the simpler case of creating a Panel App for the already present hidden
category.
Before beginning, make sure you're accustomed to using Panel Categories
and Panel Apps. This is covered in detail in the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu}{Customizing
the Product Menu} articles. Once you know how to create Panel Categories
and Panel Apps, continue with this article.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Follow the steps documented in
\href{/docs/7-2/customization/-/knowledge_base/c/adding-custom-panel-apps}{Adding
Custom Panel Apps} for creating custom Panel Apps. Once you've created
the foundation of your Panel App, move on to learn how to tweak it so
it customizes the Simulation Menu.
You can generate a Simulation Panel App by using Blade CLI's
\href{/docs/7-2/reference/-/knowledge_base/r/simulation-panel-entry-template}{Simulation
Panel Entry template}. You can also refer to the
\href{/docs/7-2/reference/-/knowledge_base/r/simulation-panel-app}{Simulation
Panel App sample} for a working example.
\item
Since this article assumes you're providing more functionality to the
existing simulation category, set the simulation category in the
\texttt{panel.category.key} of the \texttt{@Component} annotation:
\begin{verbatim}
"panel.category.key=" + SimulationPanelCategory.SIMULATION
\end{verbatim}
To use this constant, you must add a dependency on
\href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.product.navigation.simulation/}{\texttt{com.liferay.product.navigation.simulation}}.
Be sure to also specify the order to display your new Panel App, which
was explained in
\href{/docs/7-2/customization/-/knowledge_base/c/adding-custom-panel-apps}{Adding
Custom Panel Apps}.
\item
This article assumes you're using JSPs. Therefore, you should extend
the
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelApp.html}{\texttt{BaseJSPPanelApp}}
abstract class, which implements the
\href{https://docs.liferay.com/dxp/apps/application-list/latest/javadocs/com/liferay/application/list/PanelApp.html}{\texttt{PanelApp}}
interface and also provides additional methods necessary for
specifying JSPs to render your Panel App's UI. Remember that you can
also implement your own \texttt{include()} method to use any front-end
technology you want, if you want to use a technology other than JSP
(e.g., FreeMarker).
\item
Define your simulation view. For instance, in
\texttt{DevicePreviewPanelApp}, the \texttt{getJspPath} method points
to the \texttt{simulation-device.jsp} file in the
\texttt{resources/META-INF/resources} folder, where the device
simulation interface is defined. Optionally, you can also add your own
language keys, CSS, or JavaScript resources in your simulation module.
The right servlet context is also provided by implementing this
method:
\begin{verbatim}
@Override
@Reference(
target = "(osgi.web.symbolicname=com.liferay.product.navigation.simulation.device)",
unbind = "-"
)
public void setServletContext(ServletContext servletContext) {
super.setServletContext(servletContext);
}
\end{verbatim}
As explained in
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu}{Customizing
The Product Menu}, a Panel App should be associated with a portlet.
This makes the Panel App visible only when the user has permission to
view the portlet. This Panel App is associated to the Simulation
Device portlet using these methods:
\begin{verbatim}
@Override
public String getPortletId() {
return ProductNavigationSimulationPortletKeys.
PRODUCT_NAVIGATION_SIMULATION;
}
@Override
@Reference(
target = "(javax.portlet.name=" + ProductNavigationSimulationPortletKeys.PRODUCT_NAVIGATION_SIMULATION + ")",
unbind = "-"
)
public void setPortlet(Portlet portlet) {
super.setPortlet(portlet);
}
\end{verbatim}
Segments also provides a good example of how to extend the Simulation
Menu. When segments are available, the Simulation Menu is extended to
offer personalization options. You can simulate particular experiences
directly from the Simulation Menu. Its Panel App class is similar to
\texttt{DevicePreviewPanelApp}, except it points to a different
portlet and JSP. For more information on Segments, see the
\href{/docs/7-2/user/-/knowledge_base/u/segmentation-and-personalization}{Segmentation
and Personalization} section.
\begin{figure}
\centering
\includegraphics{./images/segments-preview.png}
\caption{The Simulation Menu also displays Segments to help simulate
different user experiences.}
\end{figure}
\item
You can combine your simulation options with the device simulation
options by interacting with the device preview iFrame. To retrieve the
device preview frame in an \texttt{aui:script} block of your custom
simulation view's JavaScript, you can use this code:
\begin{verbatim}
var iframe = A.one('#simulationDeviceIframe');
\end{verbatim}
Then you can modify the device preview frame URL like this:
\begin{verbatim}
iframe.setAttribute('src', newUrlWithCustomParameters);
\end{verbatim}
\end{enumerate}
Now that you know how to extend the necessary Panel Categories and Panel
Apps to modify the Simulation Menu,
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{create
a module} of your own and customize the Simulation Menu so it's most
helpful for your needs.
\chapter{Customizing the User Personal Bar and
Menu}\label{customizing-the-user-personal-bar-and-menu}
The User Personal Bar is a portlet, but it's also an important concept
in Liferay DXP. In a fresh bundle using the default theme, it's the
section of screen occupied by the User's avatar and the Personal Menu.
\begin{figure}
\centering
\includegraphics{./images/user-personal-bar.png}
\caption{By default, the User Personal Bar contains the signed-in user's
avatar, which opens the Personal Menu when selected.}
\end{figure}
The User Personal Bar holds only the Personal Menu by default, but it
can also contain any functionality you want (even additional portlets).
The User Personal Bar is included by default in every Liferay theme, but
you can replace it with a
\href{/docs/7-2/customization/-/knowledge_base/c/using-a-custom-portlet-in-place-of-the-user-personal-bar}{portlet}
or customize it by adding entries to the existing portlet's menu.
This section covers these topics:
\begin{itemize}
\tightlist
\item
Replacing the default User Personal Bar portlet with a custom portlet.
\item
Customizing the default User Personal Bar.
\end{itemize}
\section{Displaying the Personal
Menu}\label{displaying-the-personal-menu}
Starting with 7.0, the Personal Menu is no longer part of the Product
Menu, but is instead included in the User Personal Bar. To display the
existing User Personal Bar in your own theme, embed the portlet into
your theme by adding the following snippet into
\texttt{portal\_normal.ftl}:
\begin{verbatim}
<@liferay.user_personal_bar />
\end{verbatim}
You'll use the same snippet even if you're replacing the default User
Personal Bar portlet with your own.
If you use a custom portlet to provide the User Personal Bar, but wish
to include the default Personal Menu, make sure to render it by using
this tag in your portlet's JSP:
\begin{verbatim}
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} The recommended way to display the Personal Menu is by
embedding the User Personal Bar in a theme. If this is not practical, a
workaround exists: go to \emph{Control Panel} → \emph{Configuration} →
\emph{Instance Settings} → \emph{Users} and select \emph{Personal Menu}.
Enable the \emph{Show in Control Menu} toggle and click \emph{Update}.
This places a button to expand the Personal Menu in the Control Menu. It
appears on every site and page in your virtual instance, including sites
that have the User Personal Bar embedded in the theme. So, to avoid
multiple User Personal Bars appearing on the page, you should use only
\emph{one} of these mechanisms to display the User Personal Bar.
\noindent\hrulefill
Unlike the Product Menu, the Personal Menu can be customized without
creating panel categories and panel apps. See
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-personal-menu}{Customizing
the Personal Menu} for details.
\chapter{Using a Custom Portlet in Place of the User Personal
Bar}\label{using-a-custom-portlet-in-place-of-the-user-personal-bar}
In this article, you'll learn how to write the single Java class
required to replace the default User Personal Bar with a custom portlet.
Writing the portlet itself is up to each developer's needs. See the
documentation on
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{portlets} if you
need guidance.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
an OSGi module}.
\item
Create a unique package name in the module's \texttt{src} directory
and create a new Java class in that package.
\item
Above the class declaration, insert the following annotation:
\begin{verbatim}
@Component(
immediate = true,
property = {
"model.class.name=" + PortalUserPersonalBarApplicationType.UserPersonalBar.CLASS_NAME,
"service.ranking:Integer=10"
},
service = ViewPortletProvider.class
)
\end{verbatim}
The \texttt{model.class.name} property must be set to the class name
of the entity type you want the portlet to handle. In this case, you
want your portlet to be provided based on whether it can be displayed
in the User Personal Bar.
You may recall from the
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes}{Portlet
Providers} articles that you can request portlets in several different
ways (e.g., \emph{Edit}, \emph{Browse}, etc.).
You should also specify the service rank for your new portlet so it
overrides the default. Make sure to set the
\texttt{service.ranking:Integer} property to a number that is ranked
higher than the portlet being used by default.
Since you want to display your portlet instead of the User Personal
Bar, the \texttt{service} element should be
\texttt{ViewPortletProvider.class}.
\item
Update the class's declaration to extend the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BasePortletProvider.html}{\texttt{BasePortletProvider}}
abstract class and implement \texttt{ViewPortletProvider}:
\begin{verbatim}
public class ExampleViewPortletProvider extends BasePortletProvider implements ViewPortletProvider {
}
\end{verbatim}
\item
Specify the portlet you want in the User Personal Bar by declaring the
following method in your class:
\begin{verbatim}
@Override
public String getPortletName() {
return PORTLET_NAME;
}
\end{verbatim}
Replace the \texttt{PORTLET\_NAME} text with the portlet to provide
when one is requested by the theme template. For example, the default
portlet uses
\texttt{com\_liferay\_product\_navigation\_user\_personal\_bar\_web\_portlet\_ProductNavigationPersonalBarPortlet}
\end{enumerate}
If you want to inspect the entire module used for Liferay's User
Personal Bar, see the
\href{https://github.com/liferay/liferay-portal/tree/7.2.0-ga1/modules/apps/product-navigation/product-navigation-user-personal-bar-web}{product-navigation-user-personal-bar-web}
module.
\section{Related Topics}\label{related-topics-35}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu}{Customizing
the Product Menu}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu}{Customizing
the Control Menu}
\end{itemize}
\chapter{Customizing the Personal
Menu}\label{customizing-the-personal-menu}
The Personal Menu is a portlet in Liferay DXP, and is the only item
occupying the User Personal Bar out of the box. You can add entries to
the Personal Menu by implementing the \texttt{PersonalMenuEntry}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/product-navigation/product-navigation-personal-menu-api/src/main/java/com/liferay/product/navigation/personal/menu/PersonalMenuEntry.java}{interface}.
If you're adding a portlet entry to the Personal Menu, the process is
slightly different. Both approaches are covered below.
\section{Adding an Entry to the Personal
Menu}\label{adding-an-entry-to-the-personal-menu}
Follow these steps. \texttt{SignOutPersonalMenuEntry.java} is used as an
example throughout these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
an OSGi module} and place a new Java class into a package in its
\texttt{src} folder.
\item
In the \texttt{@Component} annotation, specify the two properties
shown below to place your new entry in the Personal Menu:
\begin{itemize}
\item
\texttt{product.navigation.personal.menu.group}: determines the
section where the entry will be placed.
\item
\texttt{product.navigation.personal.menu.entry.order}: determines
the order of entries within each section. Note that sections are not
labelled. To create a new section, assign the \texttt{group}
property a value other than those for the four default sections
(100, 200, 300, and 400).
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/user-personal-menu-sections.png}
\caption{The Personal Menu is organized into four sections.}
\end{figure}
Here's an example:
\begin{verbatim}
@Component(
immediate = true,
property = {
"product.navigation.personal.menu.group:Integer=400",
"product.navigation.personal.menu.entry.order:Integer=100"
},
service = PersonalMenuEntry.class
)
public class SignOutPersonalMenuEntry implements PersonalMenuEntry {
\end{verbatim}
\item
Include the interface's methods. \texttt{SignoutPersonalMenuEntry}
uses \texttt{getLabel} and \texttt{getPortletURL}, which are the only
two that are mandatory. \texttt{getLabel} retrieves a language key to
label the entry in the UI:
\begin{verbatim}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "sign-out");
}
\end{verbatim}
\texttt{getPortletURL} returns the URL for the portlet or page you
want to access with the entry:
\begin{verbatim}
public String getPortletURL(HttpServletRequest httpServletRequest)
throws PortalException {
ThemeDisplay themeDisplay =
(ThemeDisplay)httpServletRequest.getAttribute(
WebKeys.THEME_DISPLAY);
return themeDisplay.getURLSignOut();
}
}
\end{verbatim}
\end{enumerate}
That's all you need to implement the interface. However, the
\texttt{PersonalMenuEntry} interface includes a number of other methods
that you can use if you need them:
\texttt{getIcon}: identify an icon to display in the entry.
\texttt{isActive}: indicate whether the entry is currently active.
\texttt{isShow}: write logic to determine under what circumstances the
entry is displayed.
Learn how to add a portlet entry to the Personal Menu next.
\section{Adding a Portlet Entry to the Personal
Menu}\label{adding-a-portlet-entry-to-the-personal-menu}
If you're adding a portlet to the Personal Menu, you can extend the
\texttt{BasePersonalMenuEntry} class to save time. Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
an OSGi module} and place a new Java class into a package in its
\texttt{src} folder.
\item
In the \texttt{@Component} annotation, specify the two properties
shown below to place your new entry in the Personal Menu:
\begin{itemize}
\item
\texttt{product.navigation.personal.menu.group}: determines the
section where the entry will be placed.
\item
\texttt{product.navigation.personal.menu.entry.order}: determines
the order of entries within each section. Note that sections are not
labelled. To create a new section, assign the \texttt{group}
property a value other than those for the four default sections
(100, 200, 300, and 400).
\end{itemize}
An example is shown below:
\begin{verbatim}
@Component(
immediate = true,
property = {
"product.navigation.personal.menu.entry.order:Integer=100",
"product.navigation.personal.menu.group:Integer=300"
},
service = PersonalMenuEntry.class
)
public class MyAccountPersonalMenuEntry extends BasePersonalMenuEntry {
\end{verbatim}
\item
Override the \texttt{getPortletId()} method to provide the portlet's
ID, as shown in the example below:
\begin{verbatim}
public class MyAccountPersonalMenuEntry extends BasePersonalMenuEntry {
@Override
public String getPortletId() {
return UsersAdminPortletKeys.MY_ACCOUNT;
}
}
\end{verbatim}
The \texttt{BasePersonalMenuEntry} class automatically determines the
label, portlet URL, state, and visibility based on the portlet ID.
\end{enumerate}
Once you've completed your implementation and deployed your module, your
new entry is displayed in the personal menu.
\section{Related Topics}\label{related-topics-36}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu}{Customizing
the Product Menu}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu}{Customizing
the Control Menu}
\end{itemize}
\chapter{Customizing Workflow}\label{customizing-workflow}
Liferay's workflow engine calls users to participate in a review process
designed for them. Out of the box, workflow makes it possible to define
simple to complex business processes/workflows, deploy them, and manage
them through a portal interface.
Workflow is flexible, in that you can design workflow processes in XML
to suit your business needs.
In Liferay DXP version 7.2, a new set of workflow features was
introduced around the concept of
\href{/docs/7-2/customization/-/knowledge_base/c/creating-sla-calendars}{Workflow
Metrics}.
The embedded calendar that ships out of the box can be replaced by your
own custom calendar service. More customization points will likely be
added in the future.
\chapter{Creating SLA Calendars}\label{creating-sla-calendars}
By default, an internal calendar assumes the
\href{/docs/7-2/customization/-/knowledge_base/c/creating-sla-calendars}{SLA
deadline clock} should continue counting all the time: in other words,
24 hours per day, seven days per week. If you need a different calendar
format, provide your own implementation of the
\texttt{WorkflowMetricsSLACalendar} interface. New implementations of
this service are picked up automatically by the Workflow Metrics
application, so they become available as soon as the module holding the
service implementation is deployed. The interface has three methods to
implement:
\begin{verbatim}
public interface WorkflowMetricsSLACalendar {
public Duration getDuration(
LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime);
public LocalDateTime getOverdueLocalDateTime(
LocalDateTime nowLocalDateTime, Duration remainingDuration);
public String getTitle(Locale locale);
}
\end{verbatim}
If you define a new calendar, a new option becomes available in the Add
SLA form, allowing you to choose from the default 24/7 calendar or any
custom ones you've provided. For example, you can make the timer run for
8 hours per day, from 9-17 by a 24-hour clock, for 5 days per week. If
you need to, you can even stop the calendar from counting during lunch
hours!
\begin{figure}
\centering
\includegraphics{./images/workflow-custom-sla-calendar.png}
\caption{Write a Custom SLA Calendar if the default, 24/7 calendar isn't
sufficient.}
\end{figure}
\section{Dependencies}\label{dependencies-1}
Along with some artifacts you're probably used to depending on (like
\texttt{com.liferay.portal.kernel}), you'll need the
\texttt{com.liferay.portal.workflow.metrics.sla.api-{[}version{]}.jar}
artifact. For Liferay DXP version 7.2.10-GA1, here's an example Gradle
build dependency declaration:
\begin{verbatim}
compileOnly group: "com.liferay", name: "com.liferay.portal.workflow.metrics.sla.api", version: "1.1.0"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "4.4.0"
compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
\end{verbatim}
\section{Implementation Steps}\label{implementation-steps}
Implement a
\texttt{com.liferay.portal.workflow.metrics.sla.calendar.WorkflowMetricsSLACalendar}
to define your own SLA calendar logic. When you're finished, use the
created calendar when creating the
\href{/docs/7-2/customization/-/knowledge_base/c/creating-sla-calendars}{SLA
definition}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Declare the component and the class:
\begin{verbatim}
import com.liferay.portal.kernel.language.Language;
import com.liferay.portal.workflow.metrics.sla.calendar.WorkflowMetricsSLACalendar;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Locale;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(property = "sla.calendar.key=default")
public class DefaultWorkflowMetricsSLACalendar
implements WorkflowMetricsSLACalendar {
\end{verbatim}
The component property \texttt{sla.calendar.key} is required to
identify this calendar.
\item
Override \texttt{getDuration} to return the time \texttt{Duration}
when elapsed SLA time should be computed. The start and end dates that
this method receives represent the time a workflow task has been
running. For example, given a task that started at
\emph{2019-05-13T16:00:00} and finished at \emph{2019-05-13T18:00:00},
then The 24/7 calendar returns 2 elapsed hours, while a 9-17 weekdays
calendar returns 1 hour as the elapsed time.
\begin{verbatim}
@Override
public Duration getDuration(
LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime) {
return Duration.between(startLocalDateTime, endLocalDateTime);
}
\end{verbatim}
\item
\texttt{getOverdueLocalDateTime} must return the date (as a
\texttt{LocalDateTime}) when this SLA is considered overdue given the
parameter values. For example, given that
\texttt{nowLocalDateTime}=\emph{2019-05-13T17:00:00} and
\texttt{remainingDuration}=\emph{24H}, The 24/7 calendar returns a
\texttt{localDateTime} of \emph{2019-05-14T17:00:00} as the overdue
date. Given the same parameters, the 9-17 weekdays calendar should
return \emph{2019-05-17T09:00:00}. The remaining duration of time left
in the SLA is available in the method as a \texttt{Duration} object;
your job is to write logic that considers your calendar and create a
\texttt{LocalDateTime} with the proper overdue date/time.
\begin{verbatim}
@Override
public LocalDateTime getOverdueLocalDateTime(
LocalDateTime nowLocalDateTime, Duration remainingDuration) {
return nowLocalDateTime.plus(remainingDuration);
}
\end{verbatim}
\item
Use \texttt{getTitle} to provide the title for the given locale. Make
sure you
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{properly
localize} this extension by providing a \texttt{Language.properties}
file and any \texttt{Language\_xx.properties} files for translation of
the value. At runtime, the User's locale is used to return the correct
translation.
\begin{verbatim}
@Override
public String getTitle(Locale locale) {
return _language.get(locale, "default");
}
@Reference
private Language _language;
\end{verbatim}
\end{enumerate}
If the 24/7 default calendar works for you, use it. Otherwise create
your own \texttt{WorkflowMetricsSLACalendar}s.
\chapter{Customizing Core Functionality with
Ext}\label{customizing-core-functionality-with-ext}
\noindent\hrulefill
\textbf{Ext plugins are deprecated for 7.0 and should only be used if
absolutely necessary.}
The following app servers should be used for Ext plugin development in
Liferay DXP:
\begin{itemize}
\tightlist
\item
Tomcat 9.x
\end{itemize}
In most cases, Ext plugins are not necessary. There are, however,
certain cases that require the use of an Ext plugin. Liferay only
supports the following Ext plugin use cases:
\begin{itemize}
\tightlist
\item
Providing custom implementations for any beans declared in Liferay
DXP's Spring files (when possible, use
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers}{service
wrappers} instead of an Ext plugin). 7.0 removed many beans, so make
sure your overridden beans are still relevant if converting your
legacy Ext plugin
(\href{/docs/7-2/customization/-/knowledge_base/c/extending-core-classes-using-spring-with-ext-plugins}{how
to}).
\item
Overwriting a class in a 7.0 core JAR. For a list of core JARs, see
the
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts\#finding-core-artifact-attributes}{Finding
Core Liferay DXP Artifacts} section
(\href{/docs/7-2/customization/-/knowledge_base/c/overriding-core-classes-with-ext-plugins}{how
to}).
\item
Modifying Liferay DXP's \texttt{web.xml} file
(\href{/docs/7-2/customization/-/knowledge_base/c/modifying-the-web-xml-with-ext-plugins}{how
to}).
\item
Adding to Liferay DXP's \texttt{web.xml} file
(\href{/docs/7-2/customization/-/knowledge_base/c/adding-to-the-web-xml-with-ext-plugins}{how
to}).
\end{itemize}
\textbf{Note:} In previous versions of Liferay Portal, you needed an Ext
plugin to specify classes as portal property values (e.g.,
\texttt{global.starup.events.my.custom.MyStartupAction}), since the
custom class had to be added to the portal class loader. This is no
longer the case in 7.0 since all lifecycle events can use OSGi services
with no need to edit these legacy properties.
\noindent\hrulefill
Ext plugins are used to customize Liferay DXP's core functionality. You
can learn more about what the core encompasses in the
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts\#finding-core-artifact-attributes}{Finding
Core Liferay DXP Artifacts} article section. In this section, you'll
learn how to
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/creating-an-ext-plugin}{Create
an Ext plugin}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/developing-an-ext-plugin}{Develop
an Ext plugin}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin}{Deploy
an Ext plugin}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/redeploying-an-ext-plugin}{Redeploy
an Ext plugin}
\end{itemize}
You can also dive into the
\href{/docs/7-2/customization/-/knowledge_base/c/anatomy-of-an-ext-plugin}{Anatomy
of an Ext Plugin} to familiarize yourself with its structure.
You'll start by creating an Ext plugin.
\chapter{Extending Core Classes Using Spring with Ext
Plugins}\label{extending-core-classes-using-spring-with-ext-plugins}
A supported use case for using Ext plugins in Liferay DXP is extending
its core classes (e.g., \texttt{portal-impl}, \texttt{portal-kernel},
etc.) using Spring. You can reference the
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts\#finding-core-artifact-attributes}{Finding
Core Liferay Portal Artifacts} section for help distinguishing core
classes. Make sure you've reviewed the generalized
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext}{Customization
with Ext Plugins} section before creating an Ext plugin.
As an example, you'll create a sample Ext plugin that extends the
\href{https://docs.liferay.com/ce/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/util/PortalImpl.html}{PortalImpl}
core class residing in the \texttt{portal-impl.jar}. You'll override the
\texttt{PortalImpl.getComputerName()} method via Spring bean, which
returns your server's node name. The Ext plugin will override this
method and modify the server's returned node name.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your Liferay Workspace's root folder and run the following
command:
\begin{verbatim}
blade create -t war-core-ext portal-impl-extend-spring-ext
\end{verbatim}
Your Ext plugin is generated and now resides in the workspace's
\texttt{/ext} folder with the name you assigned.
\item
Displaying the server node name in your Liferay DXP installation is
set to \texttt{false} by default. You'll need to enable this property.
To do this, navigate into your Liferay bundle's root folder and create
a \texttt{portal-ext.properties} file. In that file, insert the
following property:
\begin{verbatim}
web.server.display.node=true
\end{verbatim}
Now your server's node name will be displayed once your Liferay bundle
is restarted.
\item
In the \texttt{/extImpl/java} folder, create the folder structure
representing the package name you want your new class to reside in
(e.g., \texttt{com/liferay/portal/util}). Then create your new Java
class:
\begin{verbatim}
package com.liferay.portal.util;
public class SamplePortalImpl extends PortalImpl {
@Override
public String getComputerName() {
return "SAMPLE_EXT_INSTALLED_" + super.getComputerName();
}
}
\end{verbatim}
\end{enumerate}
The method defined in the extension class overrides the
\texttt{PortalImpl.getComputerName()} method. The
\texttt{"SAMPLE\_EXT\_INSTALLED\_"} String is now prefixed to your
server's node name.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\item
In your Ext plugin's \texttt{/extImpl/resources} folder, create a
\texttt{META-INF/ext-spring.xml} file. In this file, insert the
following code:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Since you plan on modifying a core service class, you can inject its
extension class via a Spring bean. This will ensure your new class is
recognized. Assign your extension class's fully defined class name
(e.g., \texttt{com.liferay.portal.util.SamplePortalImpl}) to the bean
tag's \texttt{class} attribute and the fully defined original class name
(e.g., \texttt{com.liferay.portal.util.PortalImpl}) to the bean tag's
\texttt{id} attribute.
When your Ext plugin is deployed, your new service (e.g.,
\texttt{SamplePortalImpl}) will extend the core \texttt{PortalImpl}
class.
Awesome! You've created an Ext plugin that extends a core class in
Liferay DXP! Follow the instructions in the
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin}{Deploy
the Plugin} article to deploy it to your server.
\chapter{Overriding Core Classes with Ext
Plugins}\label{overriding-core-classes-with-ext-plugins}
A supported use case for using Ext plugins in Liferay DXP is overriding
its core classes (e.g., \texttt{portal-impl}, \texttt{portal-kernel},
etc.). You can reference the
\href{/docs/7-2/customization/-/knowledge_base/c/finding-artifacts\#finding-core-artifact-attributes}{Finding
Core Liferay Portal Artifacts} section for help distinguishing core
classes. Make sure you've reviewed the generalized
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext}{Customization
with Ext Plugins} section before creating an Ext plugin.
As an example, you'll create a sample Ext plugin that overwrites the
\href{https://docs.liferay.com/ce/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/util/PortalImpl.html}{PortalImpl}
core class residing in the \texttt{portal-impl.jar}. You'll edit the
\texttt{PortalImpl.getComputerName()} method, which returns your
server's node name. The Ext plugin will override the entire
\texttt{PortalImpl} class, adding the method modifying the server's
returned node name.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your Liferay Workspace's root folder and run the following
command:
\begin{verbatim}
blade create -t war-core-ext portal-impl-override
\end{verbatim}
Your Ext plugin is generated and now resides in the workspace's
\texttt{/ext} folder with the name you assigned.
\item
Displaying the server node name in your Liferay DXP installation is
set to \texttt{false} by default. You'll need to enable this property.
To do this, navigate into your Liferay bundle's root folder and create
a \texttt{portal-ext.properties} file. In that file, insert the
following property:
\begin{verbatim}
web.server.display.node=true
\end{verbatim}
Now your server's node name will be displayed once your Liferay bundle
is restarted.
\item
In the \texttt{/extImpl/java} folder, create the folder structure
matching the class's folder structure you'd like to override (e.g.,
\texttt{com/liferay/portal/util}). Then create the new Java class that
will override the existing core class; your new class must have the
same name as the original.
\item
Copy all of the original class's (e.g., \texttt{PortalImpl}) logic
into your new class. Then modify the method you want to customize. For
this example, you want to edit the \texttt{getComputerName()} method.
Therefore, replace it with the method below:
\begin{verbatim}
@Override
public String getComputerName() {
return "sample_portalimpl_ext_installed_successfully_" + _computerName;
}
\end{verbatim}
The method defined in the new class overrides the
\texttt{PortalImpl.getComputerName()} method. The
\texttt{sample\_portalimpl\_ext\_installed\_successfully\_} String is
now prefixed to your server's node name.
\end{enumerate}
When your Ext plugin is deployed, your new Java class will override the
core \texttt{PortalImpl} class.
Awesome! You've created an Ext plugin that overrides a core class in
Liferay DXP! Follow the instructions in the
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin}{Deploy
the Plugin} article to deploy it to your server.
\chapter{Adding to the web.xml with Ext
Plugins}\label{adding-to-the-web.xml-with-ext-plugins}
A supported use case for using Ext Plugins in Liferay DXP is adding
additional functionality to its \texttt{web.xml} file. Before beginning,
make sure you've reviewed the generalized
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext}{Customization
with Ext Plugins} section.
As an example, you'll create a sample Ext plugin that adds to your
Liferay DXP's existing \texttt{web.xml} file (e.g., in the
\texttt{/tomcat-{[}version{]}/webapps/ROOT/WEB-INF} folder). You'll add
a new printout in the console during startup.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your Liferay Workspace's root folder and run the following
command:
\begin{verbatim}
blade create -t war-core-ext add-printout
\end{verbatim}
Your Ext plugin is generated and now resides in the workspace's
\texttt{/ext} folder with the name you assigned.
\item
For your Liferay DXP installation to recognize new functionality in
the \texttt{web.xml}, you must create a class that implements the
\href{https://javaee.github.io/javaee-spec/javadocs/javax/servlet/ServletContextListener.html}{ServletContextListener}
interface. This class will initialize a servlet context event for
which you'll add your new functionality. In the \texttt{extImpl/java}
folder, create the folder structure representing the package name you
want your new class to reside in (e.g.,
\texttt{com/liferay/portal/servlet/context}). Then create your new
Java class:
\begin{verbatim}
package com.liferay.portal.servlet.context;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ExtAddEntryWebXmlPortalContextLoaderListener
implements ServletContextListener {
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("EXT_ADD_ENTRY_WEBXML_INSTALLED_SUCCESSFULLY");
}
}
\end{verbatim}
The above class includes two methods that initialize and destroy your
servlet context event. Be sure to add the new \texttt{web.xml}'s
functionality when the portal context is initializing. To add a
printout verifying the Ext plugins installation, a simple print
statement was defined in the \texttt{contextInitialized(...)} method:
\begin{verbatim}
System.out.println("EXT_ADD_ENTRY_WEBXML_INSTALLED_SUCCESSFULLY");
\end{verbatim}
\item
Now that you've defined a servlet context event, you should add a
listener to your \texttt{web.xml} that listens for it. In the
\texttt{ext-web/docroot/WEB-INF} folder, open the \texttt{web.xml}
file, which was generated for you by default.
\item
Add the following tag between the tags:
\begin{verbatim}
com.liferay.portal.servlet.context.ExtAddEntryWebXmlPortalContextLoaderListener
\end{verbatim}
\end{enumerate}
Excellent! Now when your Ext plugin is deployed, your Liferay DXP
installation will create a \texttt{ServletContextListener} instance,
which will initialize a custom servlet context event. This event will be
recognized by the \texttt{web.xml} file, which will add the new
functionality to your Liferay DXP installation. Follow the instructions
in the
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin}{Deploy
the Plugin} article for help deploying the Ext plugin to your server.
\chapter{Modifying the web.xml with Ext
Plugins}\label{modifying-the-web.xml-with-ext-plugins}
A supported use case for using Ext Plugins in Liferay DXP is modifying
its \texttt{web.xml} file. Before beginning, make sure you've reviewed
the generalized
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext}{Customization
with Ext Plugins} section.
As an example, you'll create a sample Ext plugin that modifies Liferay
DXP's existing \texttt{web.xml\ file} (e.g., in the
\texttt{/tomcat-{[}version{]}/webapps/ROOT/WEB-INF} folder). You'll
modify the session timeout configuration, which is set to 30 (minutes)
by default:
\begin{verbatim}
30
true
\end{verbatim}
The Ext plugin will update the session timeout to one minute.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate into your Liferay Workspace's \texttt{/ext} folder and run
the following command:
\begin{verbatim}
blade create -t war-core-ext modify-session-timeout
\end{verbatim}
Your Ext plugin is generated and now resides in the workspace's
\texttt{/ext} folder with the name you assigned.
\item
In the \texttt{ext-web/docroot/WEB-INF} folder, open the
\texttt{web.xml} file, which was generated for you by default.
\item
Insert the following logic between the
\texttt{\textless{}web-app\textgreater{}} tags:
\begin{verbatim}
1
true
\end{verbatim}
\end{enumerate}
Notice that the \texttt{\textless{}session-timeout\textgreater{}} tag
has been updated to \texttt{1}.
\noindent\hrulefill
\textbf{Note:} You can configure an uninterrupted session by setting the
\texttt{\textless{}session-timeout\textgreater{}} tag to \texttt{-1}.
Leaving a session permanently active is a risk and is not recommended
for production environments, but is useful for testing.
\noindent\hrulefill
That's it! Now when your Ext plugin is deployed, your Liferay DXP
installation will timeout after one minute of inactivity. Follow the
instructions in the
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin}{Deploy
the Plugin} article for help deploying the Ext plugin to your server.
================================================
FILE: book/developer/frameworks.aux
================================================
\relax
\providecommand{\transparent@use}[1]{}
\providecommand\hyper@newdestlabel[2]{}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {296}Application Security}{873}{chapter.296}\protected@file@percent }
\newlabel{application-security}{{296}{873}{Application Security}{chapter.296}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {297}Defining Application Permissions}{875}{chapter.297}\protected@file@percent }
\newlabel{defining-application-permissions}{{297}{875}{Defining Application Permissions}{chapter.297}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {298}Defining Resources and Permissions}{877}{chapter.298}\protected@file@percent }
\newlabel{defining-resources-and-permissions}{{298}{877}{Defining Resources and Permissions}{chapter.298}{}}
\@writefile{toc}{\contentsline {section}{\numberline {298.1}Defining Portlet Resource Permissions}{877}{section.298.1}\protected@file@percent }
\newlabel{defining-portlet-resource-permissions}{{298.1}{877}{Defining Portlet Resource Permissions}{section.298.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {298.2}Defining Model Resource Permissions}{879}{section.298.2}\protected@file@percent }
\newlabel{defining-model-resource-permissions}{{298.2}{879}{Defining Model Resource Permissions}{section.298.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {298.3}Enabling Your Permissions Configuration}{880}{section.298.3}\protected@file@percent }
\newlabel{enabling-your-permissions-configuration}{{298.3}{880}{Enabling Your Permissions Configuration}{section.298.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {299}Registering Permissions}{881}{chapter.299}\protected@file@percent }
\newlabel{registering-permissions}{{299}{881}{Registering Permissions}{chapter.299}{}}
\@writefile{toc}{\contentsline {section}{\numberline {299.1}Registering Permissions Resources in the Database}{881}{section.299.1}\protected@file@percent }
\newlabel{registering-permissions-resources-in-the-database}{{299.1}{881}{Registering Permissions Resources in the Database}{section.299.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {299.2}Registering Entities to the Permissions Service}{882}{section.299.2}\protected@file@percent }
\newlabel{registering-entities-to-the-permissions-service}{{299.2}{882}{Registering Entities to the Permissions Service}{section.299.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {300}Associating Permissions with Resources}{885}{chapter.300}\protected@file@percent }
\newlabel{associating-permissions-with-resources}{{300}{885}{Associating Permissions with Resources}{chapter.300}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {301}Checking Permissions}{887}{chapter.301}\protected@file@percent }
\newlabel{checking-permissions}{{301}{887}{Checking Permissions}{chapter.301}{}}
\@writefile{toc}{\contentsline {section}{\numberline {301.1}Add Permission Checks to Your Service Calls}{887}{section.301.1}\protected@file@percent }
\newlabel{add-permission-checks-to-your-service-calls}{{301.1}{887}{Add Permission Checks to Your Service Calls}{section.301.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {301.2}Create Permission Helper Classes in Your Web Module}{888}{section.301.2}\protected@file@percent }
\newlabel{create-permission-helper-classes-in-your-web-module}{{301.2}{888}{Create Permission Helper Classes in Your Web Module}{section.301.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {301.3}Add Permission Checks to Your Web Application}{890}{section.301.3}\protected@file@percent }
\newlabel{add-permission-checks-to-your-web-application}{{301.3}{890}{Add Permission Checks to Your Web Application}{section.301.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {302}Using JSR Roles in a Portlet}{893}{chapter.302}\protected@file@percent }
\newlabel{using-jsr-roles-in-a-portlet}{{302}{893}{Using JSR Roles in a Portlet}{chapter.302}{}}
\@writefile{toc}{\contentsline {section}{\numberline {302.1}JSR Portlet Security}{893}{section.302.1}\protected@file@percent }
\newlabel{jsr-portlet-security}{{302.1}{893}{JSR Portlet Security}{section.302.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {302.2}Mapping Portlet Roles to Portal Roles}{894}{section.302.2}\protected@file@percent }
\newlabel{mapping-portlet-roles-to-portal-roles}{{302.2}{894}{Mapping Portlet Roles to Portal Roles}{section.302.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {302.3}Related Topics}{895}{section.302.3}\protected@file@percent }
\newlabel{related-topics}{{302.3}{895}{Related Topics}{section.302.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {303}Authentication Pipelines}{897}{chapter.303}\protected@file@percent }
\newlabel{authentication-pipelines}{{303}{897}{Authentication Pipelines}{chapter.303}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {304}Auto Login}{899}{chapter.304}\protected@file@percent }
\newlabel{auto-login}{{304}{899}{Auto Login}{chapter.304}{}}
\@writefile{toc}{\contentsline {section}{\numberline {304.1}Creating an Auto Login Component}{899}{section.304.1}\protected@file@percent }
\newlabel{creating-an-auto-login-component}{{304.1}{899}{Creating an Auto Login Component}{section.304.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {304.2}Related Topics}{900}{section.304.2}\protected@file@percent }
\newlabel{related-topics-1}{{304.2}{900}{Related Topics}{section.304.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {305}Password-Based Authentication Pipelines}{901}{chapter.305}\protected@file@percent }
\newlabel{password-based-authentication-pipelines}{{305}{901}{Password-Based Authentication Pipelines}{chapter.305}{}}
\@writefile{toc}{\contentsline {section}{\numberline {305.1}Anatomy of an Authenticator}{901}{section.305.1}\protected@file@percent }
\newlabel{anatomy-of-an-authenticator}{{305.1}{901}{Anatomy of an Authenticator}{section.305.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {305.2}Creating an Authenticator}{902}{section.305.2}\protected@file@percent }
\newlabel{creating-an-authenticator}{{305.2}{902}{Creating an Authenticator}{section.305.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {305.1}{\ignorespaces The Authenticator module contains the validator's interface and the authenticator.}}{903}{figure.305.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {305.2}{\ignorespaces The validator project implements the Validator Interface and depends on the authenticator module.}}{906}{figure.305.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {305.3}Related Topics}{907}{section.305.3}\protected@file@percent }
\newlabel{related-topics-2}{{305.3}{907}{Related Topics}{section.305.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {306}Writing a Custom Login Portlet}{909}{chapter.306}\protected@file@percent }
\newlabel{writing-a-custom-login-portlet}{{306}{909}{Writing a Custom Login Portlet}{chapter.306}{}}
\@writefile{toc}{\contentsline {section}{\numberline {306.1}Authenticating to Liferay DXP}{909}{section.306.1}\protected@file@percent }
\newlabel{authenticating-to-liferay-dxp}{{306.1}{909}{Authenticating to Liferay DXP}{section.306.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {306.2}Related Topics}{911}{section.306.2}\protected@file@percent }
\newlabel{related-topics-3}{{306.2}{911}{Related Topics}{section.306.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {307}Service Access Policies}{913}{chapter.307}\protected@file@percent }
\newlabel{service-access-policies}{{307}{913}{Service Access Policies}{chapter.307}{}}
\@writefile{toc}{\contentsline {section}{\numberline {307.1}How Service Access Policies Work}{913}{section.307.1}\protected@file@percent }
\newlabel{how-service-access-policies-work}{{307.1}{913}{How Service Access Policies Work}{section.307.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {307.1}{\ignorespaces The authorization module maps the credentials or token to the proper Service Access Policy.}}{914}{figure.307.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {307.2}API Overview}{915}{section.307.2}\protected@file@percent }
\newlabel{api-overview}{{307.2}{915}{API Overview}{section.307.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {307.3}Service Access Policy Example}{916}{section.307.3}\protected@file@percent }
\newlabel{service-access-policy-example}{{307.3}{916}{Service Access Policy Example}{section.307.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {308}Frameworks}{919}{chapter.308}\protected@file@percent }
\newlabel{frameworks}{{308}{919}{Frameworks}{chapter.308}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {309}Asset Framework}{921}{chapter.309}\protected@file@percent }
\newlabel{asset-framework}{{309}{921}{Asset Framework}{chapter.309}{}}
\@writefile{toc}{\contentsline {section}{\numberline {309.1}Persistence Operations for Assets}{921}{section.309.1}\protected@file@percent }
\newlabel{persistence-operations-for-assets}{{309.1}{921}{Persistence Operations for Assets}{section.309.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {309.2}Rendering an Asset}{922}{section.309.2}\protected@file@percent }
\newlabel{rendering-an-asset}{{309.2}{922}{Rendering an Asset}{section.309.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {309.3}Asset Features}{922}{section.309.3}\protected@file@percent }
\newlabel{asset-features}{{309.3}{922}{Asset Features}{section.309.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {309.4}Tags and Categories}{923}{section.309.4}\protected@file@percent }
\newlabel{tags-and-categories}{{309.4}{923}{Tags and Categories}{section.309.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {309.1}{\ignorespaces Adding category and tag input options lets authors aggregate and label custom entities.}}{923}{figure.309.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {309.5}Relating Assets}{923}{section.309.5}\protected@file@percent }
\newlabel{relating-assets}{{309.5}{923}{Relating Assets}{section.309.5}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {309.2}{\ignorespaces You and your users can find it helpful to relate assets to entities, such as this blogs entry.}}{923}{figure.309.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {309.6}Implementing Asset Priority}{924}{section.309.6}\protected@file@percent }
\newlabel{implementing-asset-priority}{{309.6}{924}{Implementing Asset Priority}{section.309.6}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {309.3}{\ignorespaces The Priority field lets users set an asset's priority.}}{924}{figure.309.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {310}Adding, Updating, and Deleting Assets}{925}{chapter.310}\protected@file@percent }
\newlabel{adding-updating-and-deleting-assets}{{310}{925}{Adding, Updating, and Deleting Assets}{chapter.310}{}}
\@writefile{toc}{\contentsline {section}{\numberline {310.1}Preparing Your Project for the Asset Framework}{925}{section.310.1}\protected@file@percent }
\newlabel{preparing-your-project-for-the-asset-framework}{{310.1}{925}{Preparing Your Project for the Asset Framework}{section.310.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {310.2}Adding and Updating Assets}{925}{section.310.2}\protected@file@percent }
\newlabel{adding-and-updating-assets}{{310.2}{925}{Adding and Updating Assets}{section.310.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {310.3}Deleting Assets}{927}{section.310.3}\protected@file@percent }
\newlabel{deleting-assets}{{310.3}{927}{Deleting Assets}{section.310.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {310.1}{\ignorespaces It can be useful to show custom entities, like this wiki page entity, in a JSP or in an Asset Publisher.}}{928}{figure.310.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {311}Creating an Asset Renderer}{929}{chapter.311}\protected@file@percent }
\newlabel{creating-an-asset-renderer}{{311}{929}{Creating an Asset Renderer}{chapter.311}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {311.1}{\ignorespaces Enable printing in the Asset Publisher to display the Print icon for your asset.}}{933}{figure.311.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {312}Configuring JSP Templates for an Asset Renderer}{935}{chapter.312}\protected@file@percent }
\newlabel{configuring-jsp-templates-for-an-asset-renderer}{{312}{935}{Configuring JSP Templates for an Asset Renderer}{chapter.312}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {312.1}{\ignorespaces The abstract and full content views are rendered differently for blogs.}}{936}{figure.312.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {312.2}{\ignorespaces The \texttt {preview} template displays a preview of the asset in the Content section of the Add menu.}}{938}{figure.312.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {313}Creating a Factory for the Asset Renderer}{939}{chapter.313}\protected@file@percent }
\newlabel{creating-a-factory-for-the-asset-renderer}{{313}{939}{Creating a Factory for the Asset Renderer}{chapter.313}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {314}Implementing Asset Categorization and Tagging}{945}{chapter.314}\protected@file@percent }
\newlabel{implementing-asset-categorization-and-tagging}{{314}{945}{Implementing Asset Categorization and Tagging}{chapter.314}{}}
\@writefile{toc}{\contentsline {section}{\numberline {314.1}Adding Tags and Categories}{945}{section.314.1}\protected@file@percent }
\newlabel{adding-tags-and-categories}{{314.1}{945}{Adding Tags and Categories}{section.314.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {314.2}Displaying Tags and Categories}{945}{section.314.2}\protected@file@percent }
\newlabel{displaying-tags-and-categories}{{314.2}{945}{Displaying Tags and Categories}{section.314.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {315}Relating Assets}{947}{chapter.315}\protected@file@percent }
\newlabel{relating-assets-1}{{315}{947}{Relating Assets}{chapter.315}{}}
\@writefile{toc}{\contentsline {section}{\numberline {315.1}Relating Assets in the Service Layer}{947}{section.315.1}\protected@file@percent }
\newlabel{relating-assets-in-the-service-layer}{{315.1}{947}{Relating Assets in the Service Layer}{section.315.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {315.2}Relating Assets in the UI}{948}{section.315.2}\protected@file@percent }
\newlabel{relating-assets-in-the-ui}{{315.2}{948}{Relating Assets in the UI}{section.315.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {315.1}{\ignorespaces Your portlet's entity is now available in the Related Assets \emph {Select} menu.}}{948}{figure.315.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {315.3}Showing Related Assets}{949}{section.315.3}\protected@file@percent }
\newlabel{showing-related-assets}{{315.3}{949}{Showing Related Assets}{section.315.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {316}Implementing Asset Priority}{951}{chapter.316}\protected@file@percent }
\newlabel{implementing-asset-priority-1}{{316}{951}{Implementing Asset Priority}{chapter.316}{}}
\@writefile{toc}{\contentsline {section}{\numberline {316.1}Add the Priority Field to Your JSP}{951}{section.316.1}\protected@file@percent }
\newlabel{add-the-priority-field-to-your-jsp}{{316.1}{951}{Add the Priority Field to Your JSP}{section.316.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {316.2}Using the Priority Value in Your Service Layer}{951}{section.316.2}\protected@file@percent }
\newlabel{using-the-priority-value-in-your-service-layer}{{316.2}{951}{Using the Priority Value in Your Service Layer}{section.316.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {317}Back-end Frameworks}{953}{chapter.317}\protected@file@percent }
\newlabel{back-end-frameworks}{{317}{953}{Back-end Frameworks}{chapter.317}{}}
\@writefile{toc}{\contentsline {section}{\numberline {317.1}Portlet Providers}{953}{section.317.1}\protected@file@percent }
\newlabel{portlet-providers}{{317.1}{953}{Portlet Providers}{section.317.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {317.2}Portlet Provider Classes}{953}{section.317.2}\protected@file@percent }
\newlabel{portlet-provider-classes}{{317.2}{953}{Portlet Provider Classes}{section.317.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {317.3}Data Scopes}{954}{section.317.3}\protected@file@percent }
\newlabel{data-scopes}{{317.3}{954}{Data Scopes}{section.317.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {317.4}Accessing the Site Scope Across Apps}{954}{section.317.4}\protected@file@percent }
\newlabel{accessing-the-site-scope-across-apps}{{317.4}{954}{Accessing the Site Scope Across Apps}{section.317.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {317.5}Message Bus}{955}{section.317.5}\protected@file@percent }
\newlabel{message-bus}{{317.5}{955}{Message Bus}{section.317.5}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {317.1}{\ignorespaces JConsole shows statistics on Message Bus messages sent, messages pending, and more.}}{956}{figure.317.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {318}Creating Portlet Providers}{957}{chapter.318}\protected@file@percent }
\newlabel{creating-portlet-providers}{{318}{957}{Creating Portlet Providers}{chapter.318}{}}
\@writefile{toc}{\contentsline {section}{\numberline {318.1}Related Topics}{958}{section.318.1}\protected@file@percent }
\newlabel{related-topics-4}{{318.1}{958}{Related Topics}{section.318.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {319}Retrieving Portlets}{959}{chapter.319}\protected@file@percent }
\newlabel{retrieving-portlets}{{319}{959}{Retrieving Portlets}{chapter.319}{}}
\@writefile{toc}{\contentsline {section}{\numberline {319.1}Fetching a Portlet ID}{959}{section.319.1}\protected@file@percent }
\newlabel{fetching-a-portlet-id}{{319.1}{959}{Fetching a Portlet ID}{section.319.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {319.2}Fetching a Portlet URL}{960}{section.319.2}\protected@file@percent }
\newlabel{fetching-a-portlet-url}{{319.2}{960}{Fetching a Portlet URL}{section.319.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {319.3}Related Topics}{961}{section.319.3}\protected@file@percent }
\newlabel{related-topics-5}{{319.3}{961}{Related Topics}{section.319.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {320}Enabling and Accessing Data Scopes}{963}{chapter.320}\protected@file@percent }
\newlabel{enabling-and-accessing-data-scopes}{{320}{963}{Enabling and Accessing Data Scopes}{chapter.320}{}}
\@writefile{toc}{\contentsline {section}{\numberline {320.1}Enabling Scoping}{963}{section.320.1}\protected@file@percent }
\newlabel{enabling-scoping}{{320.1}{963}{Enabling Scoping}{section.320.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {320.2}Accessing Your App's Scope}{964}{section.320.2}\protected@file@percent }
\newlabel{accessing-your-apps-scope}{{320.2}{964}{Accessing Your App's Scope}{section.320.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {320.3}Accessing the Site Scope}{964}{section.320.3}\protected@file@percent }
\newlabel{accessing-the-site-scope}{{320.3}{964}{Accessing the Site Scope}{section.320.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {320.4}Related Topics}{965}{section.320.4}\protected@file@percent }
\newlabel{related-topics-6}{{320.4}{965}{Related Topics}{section.320.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {321}Using the Message Bus}{967}{chapter.321}\protected@file@percent }
\newlabel{using-the-message-bus}{{321}{967}{Using the Message Bus}{chapter.321}{}}
\@writefile{toc}{\contentsline {section}{\numberline {321.1}Messaging Destinations}{967}{section.321.1}\protected@file@percent }
\newlabel{messaging-destinations}{{321.1}{967}{Messaging Destinations}{section.321.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {321.2}Destination Configuration}{967}{section.321.2}\protected@file@percent }
\newlabel{destination-configuration}{{321.2}{967}{Destination Configuration}{section.321.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {321.3}Message Listeners}{968}{section.321.3}\protected@file@percent }
\newlabel{message-listeners}{{321.3}{968}{Message Listeners}{section.321.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {321.4}Sending Messages}{969}{section.321.4}\protected@file@percent }
\newlabel{sending-messages}{{321.4}{969}{Sending Messages}{section.321.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {322}Creating a Destination}{971}{chapter.322}\protected@file@percent }
\newlabel{creating-a-destination}{{322}{971}{Creating a Destination}{chapter.322}{}}
\@writefile{toc}{\contentsline {section}{\numberline {322.1}Related Topics}{975}{section.322.1}\protected@file@percent }
\newlabel{related-topics-7}{{322.1}{975}{Related Topics}{section.322.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {323}Message Bus Event Listeners}{977}{chapter.323}\protected@file@percent }
\newlabel{message-bus-event-listeners}{{323}{977}{Message Bus Event Listeners}{chapter.323}{}}
\@writefile{toc}{\contentsline {section}{\numberline {323.1}Listening for Destinations}{977}{section.323.1}\protected@file@percent }
\newlabel{listening-for-destinations}{{323.1}{977}{Listening for Destinations}{section.323.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {323.2}Listening for Message Listeners}{977}{section.323.2}\protected@file@percent }
\newlabel{listening-for-message-listeners}{{323.2}{977}{Listening for Message Listeners}{section.323.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {323.3}Related Topics}{978}{section.323.3}\protected@file@percent }
\newlabel{related-topics-8}{{323.3}{978}{Related Topics}{section.323.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {324}Registering Message Listeners}{979}{chapter.324}\protected@file@percent }
\newlabel{registering-message-listeners}{{324}{979}{Registering Message Listeners}{chapter.324}{}}
\@writefile{toc}{\contentsline {section}{\numberline {324.1}Automatic Registration as a Component}{979}{section.324.1}\protected@file@percent }
\newlabel{automatic-registration-as-a-component}{{324.1}{979}{Automatic Registration as a Component}{section.324.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {324.2}Registering via a MessageBus Reference}{980}{section.324.2}\protected@file@percent }
\newlabel{registering-via-a-messagebus-reference}{{324.2}{980}{Registering via a MessageBus Reference}{section.324.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {324.3}Registering Directly to the Destination}{980}{section.324.3}\protected@file@percent }
\newlabel{registering-directly-to-the-destination}{{324.3}{980}{Registering Directly to the Destination}{section.324.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {324.4}Related Topics}{981}{section.324.4}\protected@file@percent }
\newlabel{related-topics-9}{{324.4}{981}{Related Topics}{section.324.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {325}Creating a Message}{983}{chapter.325}\protected@file@percent }
\newlabel{creating-a-message}{{325}{983}{Creating a Message}{chapter.325}{}}
\@writefile{toc}{\contentsline {section}{\numberline {325.1}Related Topics}{983}{section.325.1}\protected@file@percent }
\newlabel{related-topics-10}{{325.1}{983}{Related Topics}{section.325.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {326}Sending a Message}{985}{chapter.326}\protected@file@percent }
\newlabel{sending-a-message}{{326}{985}{Sending a Message}{chapter.326}{}}
\@writefile{toc}{\contentsline {section}{\numberline {326.1}Directly with MessageBus}{985}{section.326.1}\protected@file@percent }
\newlabel{directly-with-messagebus}{{326.1}{985}{Directly with MessageBus}{section.326.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {326.2}Asynchronously with SingleDestinationMessageSender}{986}{section.326.2}\protected@file@percent }
\newlabel{asynchronously-with-singledestinationmessagesender}{{326.2}{986}{Asynchronously with SingleDestinationMessageSender}{section.326.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {326.3}Synchronously with SynchronousMessageSender}{987}{section.326.3}\protected@file@percent }
\newlabel{synchronously-with-synchronousmessagesender}{{326.3}{987}{Synchronously with SynchronousMessageSender}{section.326.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {326.4}Related Topics}{988}{section.326.4}\protected@file@percent }
\newlabel{related-topics-11}{{326.4}{988}{Related Topics}{section.326.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {327}Sending Messages Across a Cluster}{989}{chapter.327}\protected@file@percent }
\newlabel{sending-messages-across-a-cluster}{{327}{989}{Sending Messages Across a Cluster}{chapter.327}{}}
\@writefile{toc}{\contentsline {section}{\numberline {327.1}Related Topics}{990}{section.327.1}\protected@file@percent }
\newlabel{related-topics-12}{{327.1}{990}{Related Topics}{section.327.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {328}Cache Configuration}{991}{chapter.328}\protected@file@percent }
\newlabel{cache-configuration}{{328}{991}{Cache Configuration}{chapter.328}{}}
\@writefile{toc}{\contentsline {section}{\numberline {328.1}Cache Types}{991}{section.328.1}\protected@file@percent }
\newlabel{cache-types}{{328.1}{991}{Cache Types}{section.328.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {328.2}Cache Configuration}{992}{section.328.2}\protected@file@percent }
\newlabel{cache-configuration-1}{{328.2}{992}{Cache Configuration}{section.328.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {328.3}Initial Global Cache Configuration}{992}{section.328.3}\protected@file@percent }
\newlabel{initial-global-cache-configuration}{{328.3}{992}{Initial Global Cache Configuration}{section.328.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {328.4}Module Cache Configuration}{992}{section.328.4}\protected@file@percent }
\newlabel{module-cache-configuration}{{328.4}{992}{Module Cache Configuration}{section.328.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {328.5}Portlet WAR Cache Configuration}{993}{section.328.5}\protected@file@percent }
\newlabel{portlet-war-cache-configuration}{{328.5}{993}{Portlet WAR Cache Configuration}{section.328.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {328.6}Cache Names and Registration}{993}{section.328.6}\protected@file@percent }
\newlabel{cache-names-and-registration}{{328.6}{993}{Cache Names and Registration}{section.328.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {328.7}EntityCache Names}{993}{section.328.7}\protected@file@percent }
\newlabel{entitycache-names}{{328.7}{993}{EntityCache Names}{section.328.7}{}}
\gdef \LT@xv {\LT@entry
{1}{105.04608pt}\LT@entry
{1}{185.35446pt}\LT@entry
{1}{179.35446pt}}
\@writefile{toc}{\contentsline {section}{\numberline {328.8}FinderCache Names}{994}{section.328.8}\protected@file@percent }
\newlabel{findercache-names}{{328.8}{994}{FinderCache Names}{section.328.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {329}Overriding Cache}{995}{chapter.329}\protected@file@percent }
\newlabel{overriding-cache}{{329}{995}{Overriding Cache}{chapter.329}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {329.1}{\ignorespaces Caches configured in Liferay DXP can be examined using JMX tools such as Zulu Mission Control (Portal Process → MBean server → MBean Browser)}}{996}{figure.329.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {329.1}Related Topics}{997}{section.329.1}\protected@file@percent }
\newlabel{related-topics-13}{{329.1}{997}{Related Topics}{section.329.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {330}Caching Data}{999}{chapter.330}\protected@file@percent }
\newlabel{caching-data}{{330}{999}{Caching Data}{chapter.330}{}}
\@writefile{toc}{\contentsline {section}{\numberline {330.1}Step 1: Determine Cache Pool Requirements}{999}{section.330.1}\protected@file@percent }
\newlabel{step-1-determine-cache-pool-requirements}{{330.1}{999}{Step 1: Determine Cache Pool Requirements}{section.330.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {330.2}Step 2: Implement a Cache Key}{999}{section.330.2}\protected@file@percent }
\newlabel{step-2-implement-a-cache-key}{{330.2}{999}{Step 2: Implement a Cache Key}{section.330.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {330.3}Step 3: Implement Cache Logic}{1001}{section.330.3}\protected@file@percent }
\newlabel{step-3-implement-cache-logic}{{330.3}{1001}{Step 3: Implement Cache Logic}{section.330.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {330.4}Step 4: Configure the Cache}{1002}{section.330.4}\protected@file@percent }
\newlabel{step-4-configure-the-cache}{{330.4}{1002}{Step 4: Configure the Cache}{section.330.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {330.5}Related Topics}{1003}{section.330.5}\protected@file@percent }
\newlabel{related-topics-14}{{330.5}{1003}{Related Topics}{section.330.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {331}Collaboration}{1005}{chapter.331}\protected@file@percent }
\newlabel{collaboration}{{331}{1005}{Collaboration}{chapter.331}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {332}Item Selector}{1007}{chapter.332}\protected@file@percent }
\newlabel{item-selector}{{332}{1007}{Item Selector}{chapter.332}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {332.1}{\ignorespaces Item Selectors select different kinds of entities.}}{1007}{figure.332.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {333}Adaptive Media}{1009}{chapter.333}\protected@file@percent }
\newlabel{adaptive-media}{{333}{1009}{Adaptive Media}{chapter.333}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {334}Social API}{1011}{chapter.334}\protected@file@percent }
\newlabel{social-api}{{334}{1011}{Social API}{chapter.334}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {335}Documents and Media API}{1013}{chapter.335}\protected@file@percent }
\newlabel{documents-and-media-api}{{335}{1013}{Documents and Media API}{chapter.335}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {336}Item Selector}{1015}{chapter.336}\protected@file@percent }
\newlabel{item-selector-1}{{336}{1015}{Item Selector}{chapter.336}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {336.1}{\ignorespaces Item Selectors select entities.}}{1016}{figure.336.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {337}Understanding the Item Selector API's Components}{1017}{chapter.337}\protected@file@percent }
\newlabel{understanding-the-item-selector-apis-components}{{337}{1017}{Understanding the Item Selector API's Components}{chapter.337}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {337.1}{\ignorespaces Item Selector views (selection views) are determined by the return type and criterion, and rendered by the markup.}}{1018}{figure.337.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {338}Getting an Item Selector}{1019}{chapter.338}\protected@file@percent }
\newlabel{getting-an-item-selector}{{338}{1019}{Getting an Item Selector}{chapter.338}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {339}Understanding Custom Selection Views}{1021}{chapter.339}\protected@file@percent }
\newlabel{understanding-custom-selection-views}{{339}{1021}{Understanding Custom Selection Views}{chapter.339}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {339.1}{\ignorespaces An entity type can have multiple selection views.}}{1021}{figure.339.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {339.1}The Selection View's Class}{1021}{section.339.1}\protected@file@percent }
\newlabel{the-selection-views-class}{{339.1}{1021}{The Selection View's Class}{section.339.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {340}Selecting Entities with an Item Selector}{1023}{chapter.340}\protected@file@percent }
\newlabel{selecting-entities-with-an-item-selector}{{340}{1023}{Selecting Entities with an Item Selector}{chapter.340}{}}
\@writefile{toc}{\contentsline {section}{\numberline {340.1}Get an Item Selector}{1023}{section.340.1}\protected@file@percent }
\newlabel{get-an-item-selector}{{340.1}{1023}{Get an Item Selector}{section.340.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {340.2}Using the Item Selector Dialog}{1024}{section.340.2}\protected@file@percent }
\newlabel{using-the-item-selector-dialog}{{340.2}{1024}{Using the Item Selector Dialog}{section.340.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {340.3}Related Topics}{1027}{section.340.3}\protected@file@percent }
\newlabel{related-topics-15}{{340.3}{1027}{Related Topics}{section.340.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {341}Creating Custom Criterion and Return Types}{1029}{chapter.341}\protected@file@percent }
\newlabel{creating-custom-criterion-and-return-types}{{341}{1029}{Creating Custom Criterion and Return Types}{chapter.341}{}}
\@writefile{toc}{\contentsline {section}{\numberline {341.1}Related Topics}{1030}{section.341.1}\protected@file@percent }
\newlabel{related-topics-16}{{341.1}{1030}{Related Topics}{section.341.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {342}Creating Custom Item Selector Views}{1031}{chapter.342}\protected@file@percent }
\newlabel{creating-custom-item-selector-views}{{342}{1031}{Creating Custom Item Selector Views}{chapter.342}{}}
\@writefile{toc}{\contentsline {section}{\numberline {342.1}Configuring Your Selection View's OSGi Module}{1031}{section.342.1}\protected@file@percent }
\newlabel{configuring-your-selection-views-osgi-module}{{342.1}{1031}{Configuring Your Selection View's OSGi Module}{section.342.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {342.2}Implementing Your Selection View's Class}{1032}{section.342.2}\protected@file@percent }
\newlabel{implementing-your-selection-views-class}{{342.2}{1032}{Implementing Your Selection View's Class}{section.342.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {342.3}Writing Your View Markup}{1034}{section.342.3}\protected@file@percent }
\newlabel{writing-your-view-markup}{{342.3}{1034}{Writing Your View Markup}{section.342.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {342.1}{\ignorespaces The Layouts Item Selector view uses Lexicon and Liferay Layout taglibs to create the UI.}}{1035}{figure.342.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {342.2}{\ignorespaces The URL and UUID can be seen in the \texttt {data-url} and \texttt {data-uuid} attributes of the Layout Item Selector's HTML.}}{1037}{figure.342.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {342.4}Related Topics}{1037}{section.342.4}\protected@file@percent }
\newlabel{related-topics-17}{{342.4}{1037}{Related Topics}{section.342.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {343}Documents and Media API}{1039}{chapter.343}\protected@file@percent }
\newlabel{documents-and-media-api-1}{{343}{1039}{Documents and Media API}{chapter.343}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {344}Getting Started with the Documents and Media API}{1041}{chapter.344}\protected@file@percent }
\newlabel{getting-started-with-the-documents-and-media-api}{{344}{1041}{Getting Started with the Documents and Media API}{chapter.344}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {345}Key Interfaces}{1043}{chapter.345}\protected@file@percent }
\newlabel{key-interfaces}{{345}{1043}{Key Interfaces}{chapter.345}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {346}Getting a Service Reference}{1045}{chapter.346}\protected@file@percent }
\newlabel{getting-a-service-reference}{{346}{1045}{Getting a Service Reference}{chapter.346}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {347}Specifying Repositories}{1047}{chapter.347}\protected@file@percent }
\newlabel{specifying-repositories}{{347}{1047}{Specifying Repositories}{chapter.347}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {348}Specifying Folders}{1049}{chapter.348}\protected@file@percent }
\newlabel{specifying-folders}{{348}{1049}{Specifying Folders}{chapter.348}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {349}Creating Files, Folders, and Shortcuts}{1051}{chapter.349}\protected@file@percent }
\newlabel{creating-files-folders-and-shortcuts}{{349}{1051}{Creating Files, Folders, and Shortcuts}{chapter.349}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {350}Files}{1053}{chapter.350}\protected@file@percent }
\newlabel{files}{{350}{1053}{Files}{chapter.350}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {351}Folders}{1055}{chapter.351}\protected@file@percent }
\newlabel{folders}{{351}{1055}{Folders}{chapter.351}{}}
\@writefile{toc}{\contentsline {section}{\numberline {351.1}Folders and External Repositories}{1055}{section.351.1}\protected@file@percent }
\newlabel{folders-and-external-repositories}{{351.1}{1055}{Folders and External Repositories}{section.351.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {352}File Shortcuts}{1057}{chapter.352}\protected@file@percent }
\newlabel{file-shortcuts}{{352}{1057}{File Shortcuts}{chapter.352}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {353}Creating Files}{1059}{chapter.353}\protected@file@percent }
\newlabel{creating-files}{{353}{1059}{Creating Files}{chapter.353}{}}
\@writefile{toc}{\contentsline {section}{\numberline {353.1}Related Topics}{1060}{section.353.1}\protected@file@percent }
\newlabel{related-topics-18}{{353.1}{1060}{Related Topics}{section.353.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {354}Creating Folders}{1061}{chapter.354}\protected@file@percent }
\newlabel{creating-folders}{{354}{1061}{Creating Folders}{chapter.354}{}}
\@writefile{toc}{\contentsline {section}{\numberline {354.1}Related Topics}{1062}{section.354.1}\protected@file@percent }
\newlabel{related-topics-19}{{354.1}{1062}{Related Topics}{section.354.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {355}Creating File Shortcuts}{1063}{chapter.355}\protected@file@percent }
\newlabel{creating-file-shortcuts}{{355}{1063}{Creating File Shortcuts}{chapter.355}{}}
\@writefile{toc}{\contentsline {section}{\numberline {355.1}Related Topics}{1064}{section.355.1}\protected@file@percent }
\newlabel{related-topics-20}{{355.1}{1064}{Related Topics}{section.355.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {356}Deleting Entities}{1065}{chapter.356}\protected@file@percent }
\newlabel{deleting-entities}{{356}{1065}{Deleting Entities}{chapter.356}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {357}Files}{1067}{chapter.357}\protected@file@percent }
\newlabel{files-1}{{357}{1067}{Files}{chapter.357}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {358}File Versions}{1069}{chapter.358}\protected@file@percent }
\newlabel{file-versions}{{358}{1069}{File Versions}{chapter.358}{}}
\@writefile{toc}{\contentsline {section}{\numberline {358.1}Identifying File Versions}{1069}{section.358.1}\protected@file@percent }
\newlabel{identifying-file-versions}{{358.1}{1069}{Identifying File Versions}{section.358.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {359}File Shortcuts}{1071}{chapter.359}\protected@file@percent }
\newlabel{file-shortcuts-1}{{359}{1071}{File Shortcuts}{chapter.359}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {360}Folders}{1073}{chapter.360}\protected@file@percent }
\newlabel{folders-1}{{360}{1073}{Folders}{chapter.360}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {361}Recycle Bin}{1075}{chapter.361}\protected@file@percent }
\newlabel{recycle-bin}{{361}{1075}{Recycle Bin}{chapter.361}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {362}Deleting Files}{1077}{chapter.362}\protected@file@percent }
\newlabel{deleting-files}{{362}{1077}{Deleting Files}{chapter.362}{}}
\@writefile{toc}{\contentsline {section}{\numberline {362.1}Related Topics}{1078}{section.362.1}\protected@file@percent }
\newlabel{related-topics-21}{{362.1}{1078}{Related Topics}{section.362.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {363}Deleting File Versions}{1079}{chapter.363}\protected@file@percent }
\newlabel{deleting-file-versions}{{363}{1079}{Deleting File Versions}{chapter.363}{}}
\@writefile{toc}{\contentsline {section}{\numberline {363.1}Related Topics}{1080}{section.363.1}\protected@file@percent }
\newlabel{related-topics-22}{{363.1}{1080}{Related Topics}{section.363.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {364}Deleting File Shortcuts}{1081}{chapter.364}\protected@file@percent }
\newlabel{deleting-file-shortcuts}{{364}{1081}{Deleting File Shortcuts}{chapter.364}{}}
\@writefile{toc}{\contentsline {section}{\numberline {364.1}Related Topics}{1081}{section.364.1}\protected@file@percent }
\newlabel{related-topics-23}{{364.1}{1081}{Related Topics}{section.364.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {365}Deleting Folders}{1083}{chapter.365}\protected@file@percent }
\newlabel{deleting-folders}{{365}{1083}{Deleting Folders}{chapter.365}{}}
\@writefile{toc}{\contentsline {section}{\numberline {365.1}Related Topics}{1084}{section.365.1}\protected@file@percent }
\newlabel{related-topics-24}{{365.1}{1084}{Related Topics}{section.365.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {366}Moving Entities to the Recycle Bin}{1085}{chapter.366}\protected@file@percent }
\newlabel{moving-entities-to-the-recycle-bin}{{366}{1085}{Moving Entities to the Recycle Bin}{chapter.366}{}}
\@writefile{toc}{\contentsline {section}{\numberline {366.1}Related Topics}{1085}{section.366.1}\protected@file@percent }
\newlabel{related-topics-25}{{366.1}{1085}{Related Topics}{section.366.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {367}Updating Entities}{1087}{chapter.367}\protected@file@percent }
\newlabel{updating-entities}{{367}{1087}{Updating Entities}{chapter.367}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {368}Files}{1089}{chapter.368}\protected@file@percent }
\newlabel{files-2}{{368}{1089}{Files}{chapter.368}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {369}Folders}{1091}{chapter.369}\protected@file@percent }
\newlabel{folders-2}{{369}{1091}{Folders}{chapter.369}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {370}File Shortcuts}{1093}{chapter.370}\protected@file@percent }
\newlabel{file-shortcuts-2}{{370}{1093}{File Shortcuts}{chapter.370}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {371}Updating Files}{1095}{chapter.371}\protected@file@percent }
\newlabel{updating-files}{{371}{1095}{Updating Files}{chapter.371}{}}
\@writefile{toc}{\contentsline {section}{\numberline {371.1}Related Topics}{1096}{section.371.1}\protected@file@percent }
\newlabel{related-topics-26}{{371.1}{1096}{Related Topics}{section.371.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {372}Updating Folders}{1097}{chapter.372}\protected@file@percent }
\newlabel{updating-folders}{{372}{1097}{Updating Folders}{chapter.372}{}}
\@writefile{toc}{\contentsline {section}{\numberline {372.1}Related Topics}{1098}{section.372.1}\protected@file@percent }
\newlabel{related-topics-27}{{372.1}{1098}{Related Topics}{section.372.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {373}Updating File Shortcuts}{1099}{chapter.373}\protected@file@percent }
\newlabel{updating-file-shortcuts}{{373}{1099}{Updating File Shortcuts}{chapter.373}{}}
\@writefile{toc}{\contentsline {section}{\numberline {373.1}Related Topics}{1100}{section.373.1}\protected@file@percent }
\newlabel{related-topics-28}{{373.1}{1100}{Related Topics}{section.373.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {374}File Checkout and Checkin}{1101}{chapter.374}\protected@file@percent }
\newlabel{file-checkout-and-checkin}{{374}{1101}{File Checkout and Checkin}{chapter.374}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {375}File Checkout}{1103}{chapter.375}\protected@file@percent }
\newlabel{file-checkout}{{375}{1103}{File Checkout}{chapter.375}{}}
\@writefile{toc}{\contentsline {section}{\numberline {375.1}Fine-tuning Checkout}{1103}{section.375.1}\protected@file@percent }
\newlabel{fine-tuning-checkout}{{375.1}{1103}{Fine-tuning Checkout}{section.375.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {376}File Checkin}{1105}{chapter.376}\protected@file@percent }
\newlabel{file-checkin}{{376}{1105}{File Checkin}{chapter.376}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {377}Canceling a Checkout}{1107}{chapter.377}\protected@file@percent }
\newlabel{canceling-a-checkout}{{377}{1107}{Canceling a Checkout}{chapter.377}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {378}Checking Out Files}{1109}{chapter.378}\protected@file@percent }
\newlabel{checking-out-files}{{378}{1109}{Checking Out Files}{chapter.378}{}}
\@writefile{toc}{\contentsline {section}{\numberline {378.1}Related Topics}{1109}{section.378.1}\protected@file@percent }
\newlabel{related-topics-29}{{378.1}{1109}{Related Topics}{section.378.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {379}Checking In Files}{1111}{chapter.379}\protected@file@percent }
\newlabel{checking-in-files}{{379}{1111}{Checking In Files}{chapter.379}{}}
\@writefile{toc}{\contentsline {section}{\numberline {379.1}Related Topics}{1112}{section.379.1}\protected@file@percent }
\newlabel{related-topics-30}{{379.1}{1112}{Related Topics}{section.379.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {380}Canceling a Checkout}{1113}{chapter.380}\protected@file@percent }
\newlabel{canceling-a-checkout-1}{{380}{1113}{Canceling a Checkout}{chapter.380}{}}
\@writefile{toc}{\contentsline {section}{\numberline {380.1}Related Topics}{1113}{section.380.1}\protected@file@percent }
\newlabel{related-topics-31}{{380.1}{1113}{Related Topics}{section.380.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {381}Copying and Moving Entities}{1115}{chapter.381}\protected@file@percent }
\newlabel{copying-and-moving-entities}{{381}{1115}{Copying and Moving Entities}{chapter.381}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {382}Copying Folders}{1117}{chapter.382}\protected@file@percent }
\newlabel{copying-folders}{{382}{1117}{Copying Folders}{chapter.382}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {383}Moving Folders and Files}{1119}{chapter.383}\protected@file@percent }
\newlabel{moving-folders-and-files}{{383}{1119}{Moving Folders and Files}{chapter.383}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {384}Copying Folders}{1121}{chapter.384}\protected@file@percent }
\newlabel{copying-folders-1}{{384}{1121}{Copying Folders}{chapter.384}{}}
\@writefile{toc}{\contentsline {section}{\numberline {384.1}Related Topics}{1122}{section.384.1}\protected@file@percent }
\newlabel{related-topics-32}{{384.1}{1122}{Related Topics}{section.384.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {385}Moving Folders and Files}{1123}{chapter.385}\protected@file@percent }
\newlabel{moving-folders-and-files-1}{{385}{1123}{Moving Folders and Files}{chapter.385}{}}
\@writefile{toc}{\contentsline {section}{\numberline {385.1}Related Topics}{1124}{section.385.1}\protected@file@percent }
\newlabel{related-topics-33}{{385.1}{1124}{Related Topics}{section.385.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {386}Getting Entities}{1125}{chapter.386}\protected@file@percent }
\newlabel{getting-entities}{{386}{1125}{Getting Entities}{chapter.386}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {387}Files}{1127}{chapter.387}\protected@file@percent }
\newlabel{files-3}{{387}{1127}{Files}{chapter.387}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {388}Folders}{1129}{chapter.388}\protected@file@percent }
\newlabel{folders-3}{{388}{1129}{Folders}{chapter.388}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {389}Multiple Entity Types}{1131}{chapter.389}\protected@file@percent }
\newlabel{multiple-entity-types}{{389}{1131}{Multiple Entity Types}{chapter.389}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {390}Getting Files}{1133}{chapter.390}\protected@file@percent }
\newlabel{getting-files}{{390}{1133}{Getting Files}{chapter.390}{}}
\@writefile{toc}{\contentsline {section}{\numberline {390.1}Related Topics}{1134}{section.390.1}\protected@file@percent }
\newlabel{related-topics-34}{{390.1}{1134}{Related Topics}{section.390.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {391}Getting Folders}{1135}{chapter.391}\protected@file@percent }
\newlabel{getting-folders}{{391}{1135}{Getting Folders}{chapter.391}{}}
\@writefile{toc}{\contentsline {section}{\numberline {391.1}Related Topics}{1136}{section.391.1}\protected@file@percent }
\newlabel{related-topics-35}{{391.1}{1136}{Related Topics}{section.391.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {392}Getting Multiple Entity Types}{1137}{chapter.392}\protected@file@percent }
\newlabel{getting-multiple-entity-types}{{392}{1137}{Getting Multiple Entity Types}{chapter.392}{}}
\@writefile{toc}{\contentsline {section}{\numberline {392.1}Related Topics}{1138}{section.392.1}\protected@file@percent }
\newlabel{related-topics-36}{{392.1}{1138}{Related Topics}{section.392.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {393}Adaptive Media}{1139}{chapter.393}\protected@file@percent }
\newlabel{adaptive-media-1}{{393}{1139}{Adaptive Media}{chapter.393}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {394}The Adaptive Media Taglib}{1141}{chapter.394}\protected@file@percent }
\newlabel{the-adaptive-media-taglib}{{394}{1141}{The Adaptive Media Taglib}{chapter.394}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {395}Adaptive Media's Finder API}{1143}{chapter.395}\protected@file@percent }
\newlabel{adaptive-medias-finder-api}{{395}{1143}{Adaptive Media's Finder API}{chapter.395}{}}
\@writefile{toc}{\contentsline {section}{\numberline {395.1}Calling the API}{1143}{section.395.1}\protected@file@percent }
\newlabel{calling-the-api}{{395.1}{1143}{Calling the API}{section.395.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {395.2}Adaptive Media API Constants}{1144}{section.395.2}\protected@file@percent }
\newlabel{adaptive-media-api-constants}{{395.2}{1144}{Adaptive Media API Constants}{section.395.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {395.3}Approximate Attributes}{1144}{section.395.3}\protected@file@percent }
\newlabel{approximate-attributes}{{395.3}{1144}{Approximate Attributes}{section.395.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {396}Image Scaling in Adaptive Media}{1145}{chapter.396}\protected@file@percent }
\newlabel{image-scaling-in-adaptive-media}{{396}{1145}{Image Scaling in Adaptive Media}{chapter.396}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {397}Displaying Adapted Images in Your App}{1147}{chapter.397}\protected@file@percent }
\newlabel{displaying-adapted-images-in-your-app}{{397}{1147}{Displaying Adapted Images in Your App}{chapter.397}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {397.1}{\ignorespaces The Adaptive Media Samples app shows all the site's adapted images.}}{1148}{figure.397.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {397.1}Related Topics}{1148}{section.397.1}\protected@file@percent }
\newlabel{related-topics-37}{{397.1}{1148}{Related Topics}{section.397.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {398}Finding Adapted Images}{1151}{chapter.398}\protected@file@percent }
\newlabel{finding-adapted-images}{{398}{1151}{Finding Adapted Images}{chapter.398}{}}
\@writefile{toc}{\contentsline {section}{\numberline {398.1}Getting Adapted Images for File Versions}{1151}{section.398.1}\protected@file@percent }
\newlabel{getting-adapted-images-for-file-versions}{{398.1}{1151}{Getting Adapted Images for File Versions}{section.398.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {398.2}Getting the Adapted Images for a Specific Image Resolution}{1152}{section.398.2}\protected@file@percent }
\newlabel{getting-the-adapted-images-for-a-specific-image-resolution}{{398.2}{1152}{Getting the Adapted Images for a Specific Image Resolution}{section.398.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {398.3}Getting Adapted Images in a Specific Order}{1152}{section.398.3}\protected@file@percent }
\newlabel{getting-adapted-images-in-a-specific-order}{{398.3}{1152}{Getting Adapted Images in a Specific Order}{section.398.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {398.4}Using Approximate Attributes}{1153}{section.398.4}\protected@file@percent }
\newlabel{using-approximate-attributes}{{398.4}{1153}{Using Approximate Attributes}{section.398.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {398.5}Using the Adaptive Media Stream}{1153}{section.398.5}\protected@file@percent }
\newlabel{using-the-adaptive-media-stream}{{398.5}{1153}{Using the Adaptive Media Stream}{section.398.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {398.6}Related Topics}{1154}{section.398.6}\protected@file@percent }
\newlabel{related-topics-38}{{398.6}{1154}{Related Topics}{section.398.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {399}Creating an Image Scaler}{1155}{chapter.399}\protected@file@percent }
\newlabel{creating-an-image-scaler}{{399}{1155}{Creating an Image Scaler}{chapter.399}{}}
\@writefile{toc}{\contentsline {section}{\numberline {399.1}Related Topics}{1157}{section.399.1}\protected@file@percent }
\newlabel{related-topics-39}{{399.1}{1157}{Related Topics}{section.399.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {400}Social API}{1159}{chapter.400}\protected@file@percent }
\newlabel{social-api-1}{{400}{1159}{Social API}{chapter.400}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {401}Social Bookmarks}{1161}{chapter.401}\protected@file@percent }
\newlabel{social-bookmarks}{{401}{1161}{Social Bookmarks}{chapter.401}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {401.1}{\ignorespaces With \texttt {displayStyle} set to \texttt {inline}, the first three social bookmarks appear in a row and the rest appear in a menu.}}{1161}{figure.401.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {401.2}{\ignorespaces With \texttt {displayStyle} set to \texttt {menu}, all social bookmarks appear in the \emph {Share} menu.}}{1162}{figure.401.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {402}Ratings}{1163}{chapter.402}\protected@file@percent }
\newlabel{ratings}{{402}{1163}{Ratings}{chapter.402}{}}
\@writefile{toc}{\contentsline {section}{\numberline {402.1}Rating Type Selection}{1163}{section.402.1}\protected@file@percent }
\newlabel{rating-type-selection}{{402.1}{1163}{Rating Type Selection}{section.402.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {402.2}Rating Value Transformation}{1164}{section.402.2}\protected@file@percent }
\newlabel{rating-value-transformation}{{402.2}{1164}{Rating Value Transformation}{section.402.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {403}Applying Social Bookmarks}{1165}{chapter.403}\protected@file@percent }
\newlabel{applying-social-bookmarks}{{403}{1165}{Applying Social Bookmarks}{chapter.403}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {403.1}{\ignorespaces These social bookmarks are in the inline display style.}}{1165}{figure.403.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {403.1}Related Topics}{1166}{section.403.1}\protected@file@percent }
\newlabel{related-topics-40}{{403.1}{1166}{Related Topics}{section.403.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {404}Creating Social Bookmarks}{1167}{chapter.404}\protected@file@percent }
\newlabel{creating-social-bookmarks}{{404}{1167}{Creating Social Bookmarks}{chapter.404}{}}
\@writefile{toc}{\contentsline {section}{\numberline {404.1}Implementing the SocialBookmark Interface}{1167}{section.404.1}\protected@file@percent }
\newlabel{implementing-the-socialbookmark-interface}{{404.1}{1167}{Implementing the SocialBookmark Interface}{section.404.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {404.2}Creating Your JSP}{1168}{section.404.2}\protected@file@percent }
\newlabel{creating-your-jsp}{{404.2}{1168}{Creating Your JSP}{section.404.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {404.3}Related Topics}{1169}{section.404.3}\protected@file@percent }
\newlabel{related-topics-41}{{404.3}{1169}{Related Topics}{section.404.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {405}Adding Comments to Your App}{1171}{chapter.405}\protected@file@percent }
\newlabel{adding-comments-to-your-app}{{405}{1171}{Adding Comments to Your App}{chapter.405}{}}
\@writefile{toc}{\contentsline {section}{\numberline {405.1}Related Topics}{1172}{section.405.1}\protected@file@percent }
\newlabel{related-topics-42}{{405.1}{1172}{Related Topics}{section.405.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {406}Rating Assets}{1173}{chapter.406}\protected@file@percent }
\newlabel{rating-assets}{{406}{1173}{Rating Assets}{chapter.406}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {406.1}{\ignorespaces Users can rate content to let others know how they really feel about it.}}{1173}{figure.406.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {406.1}Related Topics}{1174}{section.406.1}\protected@file@percent }
\newlabel{related-topics-43}{{406.1}{1174}{Related Topics}{section.406.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {407}Implementing Rating Type Selection}{1175}{chapter.407}\protected@file@percent }
\newlabel{implementing-rating-type-selection}{{407}{1175}{Implementing Rating Type Selection}{chapter.407}{}}
\@writefile{toc}{\contentsline {section}{\numberline {407.1}Related Topics}{1175}{section.407.1}\protected@file@percent }
\newlabel{related-topics-44}{{407.1}{1175}{Related Topics}{section.407.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {408}Customizing Rating Value Transformation}{1177}{chapter.408}\protected@file@percent }
\newlabel{customizing-rating-value-transformation}{{408}{1177}{Customizing Rating Value Transformation}{chapter.408}{}}
\@writefile{toc}{\contentsline {section}{\numberline {408.1}Related Topics}{1178}{section.408.1}\protected@file@percent }
\newlabel{related-topics-45}{{408.1}{1178}{Related Topics}{section.408.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {409}Flagging Inappropriate Asset Content}{1179}{chapter.409}\protected@file@percent }
\newlabel{flagging-inappropriate-asset-content}{{409}{1179}{Flagging Inappropriate Asset Content}{chapter.409}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {409.1}{\ignorespaces Users can flag objectionable content.}}{1179}{figure.409.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {409.1}Related Topics}{1180}{section.409.1}\protected@file@percent }
\newlabel{related-topics-46}{{409.1}{1180}{Related Topics}{section.409.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {410}Configurable Applications}{1181}{chapter.410}\protected@file@percent }
\newlabel{configurable-applications}{{410}{1181}{Configurable Applications}{chapter.410}{}}
\@writefile{toc}{\contentsline {section}{\numberline {410.1}Using a Configuration Interface}{1181}{section.410.1}\protected@file@percent }
\newlabel{using-a-configuration-interface}{{410.1}{1181}{Using a Configuration Interface}{section.410.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {410.2}Reading Configuration Values}{1183}{section.410.2}\protected@file@percent }
\newlabel{reading-configuration-values}{{410.2}{1183}{Reading Configuration Values}{section.410.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {410.3}Further Customization}{1183}{section.410.3}\protected@file@percent }
\newlabel{further-customization}{{410.3}{1183}{Further Customization}{section.410.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {411}Creating A Configuration Interface}{1185}{chapter.411}\protected@file@percent }
\newlabel{creating-a-configuration-interface}{{411}{1185}{Creating A Configuration Interface}{chapter.411}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {412}Categorizing the Configuration}{1187}{chapter.412}\protected@file@percent }
\newlabel{categorizing-the-configuration}{{412}{1187}{Categorizing the Configuration}{chapter.412}{}}
\@writefile{toc}{\contentsline {section}{\numberline {412.1}Specifying a Configuration Category}{1187}{section.412.1}\protected@file@percent }
\newlabel{specifying-a-configuration-category}{{412.1}{1187}{Specifying a Configuration Category}{section.412.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {412.2}Creating New Sections and Categories}{1188}{section.412.2}\protected@file@percent }
\newlabel{creating-new-sections-and-categories}{{412.2}{1188}{Creating New Sections and Categories}{section.412.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {413}Scoping Configurations}{1191}{chapter.413}\protected@file@percent }
\newlabel{scoping-configurations}{{413}{1191}{Scoping Configurations}{chapter.413}{}}
\@writefile{toc}{\contentsline {section}{\numberline {413.1}Step 1: Setting the Configuration Scope}{1191}{section.413.1}\protected@file@percent }
\newlabel{step-1-setting-the-configuration-scope}{{413.1}{1191}{Step 1: Setting the Configuration Scope}{section.413.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {413.2}Step 2: Enabling the Configuration for Scoped Retrieval}{1191}{section.413.2}\protected@file@percent }
\newlabel{step-2-enabling-the-configuration-for-scoped-retrieval}{{413.2}{1191}{Step 2: Enabling the Configuration for Scoped Retrieval}{section.413.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {414}Reading Scoped Configuration Values}{1193}{chapter.414}\protected@file@percent }
\newlabel{reading-scoped-configuration-values}{{414}{1193}{Reading Scoped Configuration Values}{chapter.414}{}}
\@writefile{toc}{\contentsline {section}{\numberline {414.1}Using the Configuration Provider}{1193}{section.414.1}\protected@file@percent }
\newlabel{using-the-configuration-provider}{{414.1}{1193}{Using the Configuration Provider}{section.414.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {414.2}Accessing the Portlet Instance Configuration Through the \texttt {PortletDisplay}}{1194}{section.414.2}\protected@file@percent }
\newlabel{accessing-the-portlet-instance-configuration-through-the-portletdisplay}{{414.2}{1194}{\texorpdfstring {Accessing the Portlet Instance Configuration Through the \texttt {PortletDisplay}}{Accessing the Portlet Instance Configuration Through the PortletDisplay}}{section.414.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {415}Reading Unscoped Configuration Values from an MVC Portlet}{1197}{chapter.415}\protected@file@percent }
\newlabel{reading-unscoped-configuration-values-from-an-mvc-portlet}{{415}{1197}{Reading Unscoped Configuration Values from an MVC Portlet}{chapter.415}{}}
\@writefile{toc}{\contentsline {section}{\numberline {415.1}Accessing the Configuration Object in the Portlet Class}{1197}{section.415.1}\protected@file@percent }
\newlabel{accessing-the-configuration-object-in-the-portlet-class}{{415.1}{1197}{Accessing the Configuration Object in the Portlet Class}{section.415.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {415.2}Accessing the Configuration from a JSP}{1198}{section.415.2}\protected@file@percent }
\newlabel{accessing-the-configuration-from-a-jsp}{{415.2}{1198}{Accessing the Configuration from a JSP}{section.415.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {415.3}Accessing the Configuration from the Portlet Class}{1199}{section.415.3}\protected@file@percent }
\newlabel{accessing-the-configuration-from-the-portlet-class}{{415.3}{1199}{Accessing the Configuration from the Portlet Class}{section.415.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {416}Reading Unscoped Configuration Values from a Component}{1201}{chapter.416}\protected@file@percent }
\newlabel{reading-unscoped-configuration-values-from-a-component}{{416}{1201}{Reading Unscoped Configuration Values from a Component}{chapter.416}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {417}Customizing the Configuration User Interface}{1203}{chapter.417}\protected@file@percent }
\newlabel{customizing-the-configuration-user-interface}{{417}{1203}{Customizing the Configuration User Interface}{chapter.417}{}}
\@writefile{toc}{\contentsline {section}{\numberline {417.1}Providing Custom Configuration Forms}{1203}{section.417.1}\protected@file@percent }
\newlabel{providing-custom-configuration-forms}{{417.1}{1203}{Providing Custom Configuration Forms}{section.417.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {417.2}Creating a Completely Custom Configuration UI}{1204}{section.417.2}\protected@file@percent }
\newlabel{creating-a-completely-custom-configuration-ui}{{417.2}{1204}{Creating a Completely Custom Configuration UI}{section.417.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {417.3}Excluding a Configuration UI}{1206}{section.417.3}\protected@file@percent }
\newlabel{excluding-a-configuration-ui}{{417.3}{1206}{Excluding a Configuration UI}{section.417.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {417.4}Using \texttt {generateUI}}{1206}{section.417.4}\protected@file@percent }
\newlabel{using-generateui}{{417.4}{1206}{\texorpdfstring {Using \texttt {generateUI}}{Using generateUI}}{section.417.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {417.5}Using the Configuration Visibility SPI}{1206}{section.417.5}\protected@file@percent }
\newlabel{using-the-configuration-visibility-spi}{{417.5}{1206}{Using the Configuration Visibility SPI}{section.417.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {418}Configuration Form Renderer}{1209}{chapter.418}\protected@file@percent }
\newlabel{configuration-form-renderer}{{418}{1209}{Configuration Form Renderer}{chapter.418}{}}
\@writefile{toc}{\contentsline {section}{\numberline {418.1}Creating a \texttt {DisplayContext}}{1209}{section.418.1}\protected@file@percent }
\newlabel{creating-a-displaycontext}{{418.1}{1209}{\texorpdfstring {Creating a \texttt {DisplayContext}}{Creating a DisplayContext}}{section.418.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {418.1}{\ignorespaces The auto-generated UI for the Language Template configuration screen is sub-optimal. A select list with more human readable options is preferable.}}{1210}{figure.418.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {418.2}Implementing a \texttt {ConfigurationFormRenderer}}{1210}{section.418.2}\protected@file@percent }
\newlabel{implementing-a-configurationformrenderer}{{418.2}{1210}{\texorpdfstring {Implementing a \texttt {ConfigurationFormRenderer}}{Implementing a ConfigurationFormRenderer}}{section.418.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {418.3}Writing the JSP Markup}{1213}{section.418.3}\protected@file@percent }
\newlabel{writing-the-jsp-markup}{{418.3}{1213}{Writing the JSP Markup}{section.418.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {418.2}{\ignorespaces A select list provides a more user friendly configuration experience than a text field.}}{1213}{figure.418.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {419}Using DDM Form Annotations in Configuration Forms}{1215}{chapter.419}\protected@file@percent }
\newlabel{using-ddm-form-annotations-in-configuration-forms}{{419}{1215}{Using DDM Form Annotations in Configuration Forms}{chapter.419}{}}
\@writefile{toc}{\contentsline {section}{\numberline {419.1}Step 1: Declare the Dependencies}{1215}{section.419.1}\protected@file@percent }
\newlabel{step-1-declare-the-dependencies}{{419.1}{1215}{Step 1: Declare the Dependencies}{section.419.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {419.2}Step 2: Write the Configuration Form}{1216}{section.419.2}\protected@file@percent }
\newlabel{step-2-write-the-configuration-form}{{419.2}{1216}{Step 2: Write the Configuration Form}{section.419.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {419.1}{\ignorespaces The DDM annotations are used to lay out this configuration form.}}{1217}{figure.419.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {419.3}Step 3: Write the Form Declaration}{1218}{section.419.3}\protected@file@percent }
\newlabel{step-3-write-the-form-declaration}{{419.3}{1218}{Step 3: Write the Form Declaration}{section.419.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {420}Upgrading a Legacy App}{1219}{chapter.420}\protected@file@percent }
\newlabel{upgrading-a-legacy-app}{{420}{1219}{Upgrading a Legacy App}{chapter.420}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {421}Dynamically Populating Select List Fields in the Configuration UI}{1221}{chapter.421}\protected@file@percent }
\newlabel{dynamically-populating-select-list-fields-in-the-configuration-ui}{{421}{1221}{Dynamically Populating Select List Fields in the Configuration UI}{chapter.421}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {421.1}{\ignorespaces The select list in the Google Cloud Natural Language Text Auto Tagging entry is populated programmatically, using the \texttt {ConfigurationFieldOptionsProvider}.}}{1223}{figure.421.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {422}Content Publication Management}{1225}{chapter.422}\protected@file@percent }
\newlabel{content-publication-management}{{422}{1225}{Content Publication Management}{chapter.422}{}}
\@writefile{toc}{\contentsline {section}{\numberline {422.1}Export/Import}{1225}{section.422.1}\protected@file@percent }
\newlabel{exportimport}{{422.1}{1225}{Export/Import}{section.422.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {422.1}{\ignorespaces Leveraging the Export/Import feature in your app is useful for sharing content.}}{1225}{figure.422.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {422.2}Staging}{1226}{section.422.2}\protected@file@percent }
\newlabel{staging}{{422.2}{1226}{Staging}{section.422.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {423}Export/Import}{1227}{chapter.423}\protected@file@percent }
\newlabel{exportimport-1}{{423}{1227}{Export/Import}{chapter.423}{}}
\@writefile{toc}{\contentsline {section}{\numberline {423.1}Staged Models}{1227}{section.423.1}\protected@file@percent }
\newlabel{staged-models}{{423.1}{1227}{Staged Models}{section.423.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {423.2}Data Handlers}{1228}{section.423.2}\protected@file@percent }
\newlabel{data-handlers}{{423.2}{1228}{Data Handlers}{section.423.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {423.3}Provide Entity Specific Local Services}{1228}{section.423.3}\protected@file@percent }
\newlabel{provide-entity-specific-local-services}{{423.3}{1228}{Provide Entity Specific Local Services}{section.423.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {423.4}Export/Import Event Listeners}{1228}{section.423.4}\protected@file@percent }
\newlabel{exportimport-event-listeners}{{423.4}{1228}{Export/Import Event Listeners}{section.423.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {423.1}{\ignorespaces Staged Model Repositories provide a Staging-specific layer of functionality for your local services.}}{1229}{figure.423.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {423.5}Export/Import Processes}{1230}{section.423.5}\protected@file@percent }
\newlabel{exportimport-processes}{{423.5}{1230}{Export/Import Processes}{section.423.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {424}Developing Staged Models}{1233}{chapter.424}\protected@file@percent }
\newlabel{developing-staged-models}{{424}{1233}{Developing Staged Models}{chapter.424}{}}
\@writefile{toc}{\contentsline {section}{\numberline {424.1}Staged Model Interfaces}{1233}{section.424.1}\protected@file@percent }
\newlabel{staged-model-interfaces}{{424.1}{1233}{Staged Model Interfaces}{section.424.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {424.2}Staged Model Attributes}{1234}{section.424.2}\protected@file@percent }
\newlabel{staged-model-attributes}{{424.2}{1234}{Staged Model Attributes}{section.424.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {424.3}Adapting Your Business Logic to Build Staged Models}{1234}{section.424.3}\protected@file@percent }
\newlabel{adapting-your-business-logic-to-build-staged-models}{{424.3}{1234}{Adapting Your Business Logic to Build Staged Models}{section.424.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {424.1}{\ignorespaces The Staged Model Adapter class extends your entity and staged model interfaces.}}{1235}{figure.424.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {424.2}{\ignorespaces The Model Adapter Builder gets an instance of the model and outputs a staged model.}}{1236}{figure.424.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {425}Generating Staged Models Using Service Builder}{1237}{chapter.425}\protected@file@percent }
\newlabel{generating-staged-models-using-service-builder}{{425}{1237}{Generating Staged Models Using Service Builder}{chapter.425}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {426}Creating Staged Models Manually}{1239}{chapter.426}\protected@file@percent }
\newlabel{creating-staged-models-manually}{{426}{1239}{Creating Staged Models Manually}{chapter.426}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {427}Developing Data Handlers}{1243}{chapter.427}\protected@file@percent }
\newlabel{developing-data-handlers}{{427}{1243}{Developing Data Handlers}{chapter.427}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {427.1}{\ignorespaces The Data Handler framework uses portlet data handlers and staged model data handlers to track and export/import portlet and staged model information, respectively.}}{1244}{figure.427.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {427.1}Understanding the \texttt {PortletDataHandler} Interface}{1244}{section.427.1}\protected@file@percent }
\newlabel{understanding-the-portletdatahandler-interface}{{427.1}{1244}{\texorpdfstring {Understanding the \texttt {PortletDataHandler} Interface}{Understanding the PortletDataHandler Interface}}{section.427.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {427.2}Understanding the \texttt {StagedModelDataHandler} Interface}{1245}{section.427.2}\protected@file@percent }
\newlabel{understanding-the-stagedmodeldatahandler-interface}{{427.2}{1245}{\texorpdfstring {Understanding the \texttt {StagedModelDataHandler} Interface}{Understanding the StagedModelDataHandler Interface}}{section.427.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {428}Creating Portlet Data Handlers}{1247}{chapter.428}\protected@file@percent }
\newlabel{creating-portlet-data-handlers}{{428}{1247}{Creating Portlet Data Handlers}{chapter.428}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {428.1}{\ignorespaces You can select the content types you'd like to export/import in the UI.}}{1248}{figure.428.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {428.2}{\ignorespaces The number of modified Bookmarks entities are displayed in the Export UI.}}{1251}{figure.428.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {429}Creating Staged Model Data Handlers}{1253}{chapter.429}\protected@file@percent }
\newlabel{creating-staged-model-data-handlers}{{429}{1253}{Creating Staged Model Data Handlers}{chapter.429}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {429.1}{\ignorespaces Your staged model data handler provides the display name in the Export/Import UI.}}{1254}{figure.429.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {430}Providing Entity-Specific Local Services for Export/Import}{1257}{chapter.430}\protected@file@percent }
\newlabel{providing-entity-specific-local-services-for-exportimport}{{430}{1257}{Providing Entity-Specific Local Services for Export/Import}{chapter.430}{}}
\@writefile{toc}{\contentsline {section}{\numberline {430.1}Understanding the \texttt {StagedModelRepository} Interface}{1257}{section.430.1}\protected@file@percent }
\newlabel{understanding-the-stagedmodelrepository-interface}{{430.1}{1257}{\texorpdfstring {Understanding the \texttt {StagedModelRepository} Interface}{Understanding the StagedModelRepository Interface}}{section.430.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {430.2}Using a Staged Model Repository}{1258}{section.430.2}\protected@file@percent }
\newlabel{using-a-staged-model-repository}{{430.2}{1258}{Using a Staged Model Repository}{section.430.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {431}Implementing the Staged Model Repository Framework}{1261}{chapter.431}\protected@file@percent }
\newlabel{implementing-the-staged-model-repository-framework}{{431}{1261}{Implementing the Staged Model Repository Framework}{chapter.431}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {432}Using the Staged Model Repository Framework}{1263}{chapter.432}\protected@file@percent }
\newlabel{using-the-staged-model-repository-framework}{{432}{1263}{Using the Staged Model Repository Framework}{chapter.432}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {433}Using the Export/Import Lifecycle Listener Framework}{1265}{chapter.433}\protected@file@percent }
\newlabel{using-the-exportimport-lifecycle-listener-framework}{{433}{1265}{Using the Export/Import Lifecycle Listener Framework}{chapter.433}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {434}Initiating New Export/Import Processes}{1269}{chapter.434}\protected@file@percent }
\newlabel{initiating-new-exportimport-processes}{{434}{1269}{Initiating New Export/Import Processes}{chapter.434}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {435}Staging}{1271}{chapter.435}\protected@file@percent }
\newlabel{staging-1}{{435}{1271}{Staging}{chapter.435}{}}
\@writefile{toc}{\contentsline {section}{\numberline {435.1}Controlling Staging's UI Settings}{1271}{section.435.1}\protected@file@percent }
\newlabel{controlling-stagings-ui-settings}{{435.1}{1271}{Controlling Staging's UI Settings}{section.435.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {435.2}Filtering Staging-Specific Processes and States}{1272}{section.435.2}\protected@file@percent }
\newlabel{filtering-staging-specific-processes-and-states}{{435.2}{1272}{Filtering Staging-Specific Processes and States}{section.435.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {435.1}{\ignorespaces There are many apps available to select from the Staged Content screen.}}{1273}{figure.435.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {436}Dependency Injection}{1275}{chapter.436}\protected@file@percent }
\newlabel{dependency-injection}{{436}{1275}{Dependency Injection}{chapter.436}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {437}CDI Dependency Injection}{1277}{chapter.437}\protected@file@percent }
\newlabel{cdi-dependency-injection}{{437}{1277}{CDI Dependency Injection}{chapter.437}{}}
\@writefile{toc}{\contentsline {section}{\numberline {437.1}Related Topics}{1279}{section.437.1}\protected@file@percent }
\newlabel{related-topics-47}{{437.1}{1279}{Related Topics}{section.437.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {438}OSGi CDI Integration}{1281}{chapter.438}\protected@file@percent }
\newlabel{osgi-cdi-integration}{{438}{1281}{OSGi CDI Integration}{chapter.438}{}}
\@writefile{toc}{\contentsline {section}{\numberline {438.1}Use Case: Registering a CDI bean as an OSGi service}{1281}{section.438.1}\protected@file@percent }
\newlabel{use-case-registering-a-cdi-bean-as-an-osgi-service}{{438.1}{1281}{Use Case: Registering a CDI bean as an OSGi service}{section.438.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {438.1}{\ignorespaces OSGi Service Component Runtime (SCR) finds \texttt {MyBean} as the best (highest ranked) \texttt {S1} service provider and binds it to consumer component \texttt {C1}.}}{1282}{figure.438.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {438.2}Use Case: Using an OSGi service in a bean}{1282}{section.438.2}\protected@file@percent }
\newlabel{use-case-using-an-osgi-service-in-a-bean}{{438.2}{1282}{Use Case: Using an OSGi service in a bean}{section.438.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {438.2}{\ignorespaces Here how Liferay's \texttt {UserLocalService} is injected into a bean.}}{1283}{figure.438.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {439}Publishing CDI Beans as OSGi Services}{1285}{chapter.439}\protected@file@percent }
\newlabel{publishing-cdi-beans-as-osgi-services}{{439}{1285}{Publishing CDI Beans as OSGi Services}{chapter.439}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {440}Using OSGi Services in a Bean}{1287}{chapter.440}\protected@file@percent }
\newlabel{using-osgi-services-in-a-bean}{{440}{1287}{Using OSGi Services in a Bean}{chapter.440}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {441}Declarative Services}{1289}{chapter.441}\protected@file@percent }
\newlabel{declarative-services}{{441}{1289}{Declarative Services}{chapter.441}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {442}Service Trackers for OSGi Services}{1291}{chapter.442}\protected@file@percent }
\newlabel{service-trackers-for-osgi-services}{{442}{1291}{Service Trackers for OSGi Services}{chapter.442}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {443}Using a Service Tracker}{1293}{chapter.443}\protected@file@percent }
\newlabel{using-a-service-tracker}{{443}{1293}{Using a Service Tracker}{chapter.443}{}}
\@writefile{toc}{\contentsline {section}{\numberline {443.1}Creating a New Service Tracker Where You Need It}{1293}{section.443.1}\protected@file@percent }
\newlabel{creating-a-new-service-tracker-where-you-need-it}{{443.1}{1293}{Creating a New Service Tracker Where You Need It}{section.443.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {443.2}Create a Class That Extends ServiceTracker}{1294}{section.443.2}\protected@file@percent }
\newlabel{create-a-class-that-extends-servicetracker}{{443.2}{1294}{Create a Class That Extends ServiceTracker}{section.443.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {443.3}Creating a Service Tracker that Tracks Service Events Using a Callback Handler}{1295}{section.443.3}\protected@file@percent }
\newlabel{creating-a-service-tracker-that-tracks-service-events-using-a-callback-handler}{{443.3}{1295}{Creating a Service Tracker that Tracks Service Events Using a Callback Handler}{section.443.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {444}Friendly URLs}{1297}{chapter.444}\protected@file@percent }
\newlabel{friendly-urls}{{444}{1297}{Friendly URLs}{chapter.444}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {445}Friendly URLs}{1299}{chapter.445}\protected@file@percent }
\newlabel{friendly-urls-1}{{445}{1299}{Friendly URLs}{chapter.445}{}}
\@writefile{toc}{\contentsline {section}{\numberline {445.1}Related Topics}{1302}{section.445.1}\protected@file@percent }
\newlabel{related-topics-48}{{445.1}{1302}{Related Topics}{section.445.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {446}Front-End Development}{1303}{chapter.446}\protected@file@percent }
\newlabel{front-end-development}{{446}{1303}{Front-End Development}{chapter.446}{}}
\@writefile{toc}{\contentsline {section}{\numberline {446.1}Lexicon and Clay}{1303}{section.446.1}\protected@file@percent }
\newlabel{lexicon-and-clay}{{446.1}{1303}{Lexicon and Clay}{section.446.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {446.2}Templates}{1304}{section.446.2}\protected@file@percent }
\newlabel{templates}{{446.2}{1304}{Templates}{section.446.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {446.3}Themes}{1304}{section.446.3}\protected@file@percent }
\newlabel{themes}{{446.3}{1304}{Themes}{section.446.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {446.4}Front-End Extensions}{1304}{section.446.4}\protected@file@percent }
\newlabel{front-end-extensions}{{446.4}{1304}{Front-End Extensions}{section.446.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {447}Themes}{1305}{chapter.447}\protected@file@percent }
\newlabel{themes-1}{{447}{1305}{Themes}{chapter.447}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {448}Theme Workflow}{1307}{chapter.448}\protected@file@percent }
\newlabel{theme-workflow}{{448}{1307}{Theme Workflow}{chapter.448}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {449}Developing Themes}{1309}{chapter.449}\protected@file@percent }
\newlabel{developing-themes}{{449}{1309}{Developing Themes}{chapter.449}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {450}Using Developer Mode with Themes}{1311}{chapter.450}\protected@file@percent }
\newlabel{using-developer-mode-with-themes}{{450}{1311}{Using Developer Mode with Themes}{chapter.450}{}}
\@writefile{toc}{\contentsline {section}{\numberline {450.1}Enabling Developer Mode Manually}{1311}{section.450.1}\protected@file@percent }
\newlabel{enabling-developer-mode-manually}{{450.1}{1311}{Enabling Developer Mode Manually}{section.450.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {450.2}Setting Developer Mode in Dev Studio DXP}{1311}{section.450.2}\protected@file@percent }
\newlabel{setting-developer-mode-in-dev-studio-dxp}{{450.2}{1311}{Setting Developer Mode in Dev Studio DXP}{section.450.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {450.1}{\ignorespaces The \emph {Use developer mode} option lets you enable Developer Mode for your server in Dev Studio DXP.}}{1312}{figure.450.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {450.3}Configuring FreeMarker System Settings}{1312}{section.450.3}\protected@file@percent }
\newlabel{configuring-freemarker-system-settings}{{450.3}{1312}{Configuring FreeMarker System Settings}{section.450.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {450.4}JavaScript Fast Loading}{1313}{section.450.4}\protected@file@percent }
\newlabel{javascript-fast-loading}{{450.4}{1313}{JavaScript Fast Loading}{section.450.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {450.5}Related Topics}{1313}{section.450.5}\protected@file@percent }
\newlabel{related-topics-49}{{450.5}{1313}{Related Topics}{section.450.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {451}Building Your Theme's Files}{1315}{chapter.451}\protected@file@percent }
\newlabel{building-your-themes-files}{{451}{1315}{Building Your Theme's Files}{chapter.451}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {451.1}{\ignorespaces Run the \texttt {gulp\ build} task to build your theme's files.}}{1315}{figure.451.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {451.1}Related Topics}{1315}{section.451.1}\protected@file@percent }
\newlabel{related-topics-50}{{451.1}{1315}{Related Topics}{section.451.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {452}Deploying and Applying Themes}{1317}{chapter.452}\protected@file@percent }
\newlabel{deploying-and-applying-themes}{{452}{1317}{Deploying and Applying Themes}{chapter.452}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {452.1}{\ignorespaces Your server's log notifies you when the theme's bundle has started.}}{1317}{figure.452.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {452.1}Related Topics}{1318}{section.452.1}\protected@file@percent }
\newlabel{related-topics-51}{{452.1}{1318}{Related Topics}{section.452.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {453}Updating Your Theme's App Server}{1319}{chapter.453}\protected@file@percent }
\newlabel{updating-your-themes-app-server}{{453}{1319}{Updating Your Theme's App Server}{chapter.453}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {453.1}{\ignorespaces Run the \texttt {gulp\ init} task to update your app server configuration.}}{1319}{figure.453.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {453.1}Related Topics}{1320}{section.453.1}\protected@file@percent }
\newlabel{related-topics-52}{{453.1}{1320}{Related Topics}{section.453.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {454}Automatically Deploying Theme Changes}{1321}{chapter.454}\protected@file@percent }
\newlabel{automatically-deploying-theme-changes}{{454}{1321}{Automatically Deploying Theme Changes}{chapter.454}{}}
\@writefile{toc}{\contentsline {section}{\numberline {454.1}Related Topics}{1321}{section.454.1}\protected@file@percent }
\newlabel{related-topics-53}{{454.1}{1321}{Related Topics}{section.454.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {454.1}{\ignorespaces The watch task notifies you that the changes are deployed.}}{1322}{figure.454.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {455}Creating a Thumbnail Preview for Your Theme}{1323}{chapter.455}\protected@file@percent }
\newlabel{creating-a-thumbnail-preview-for-your-theme}{{455}{1323}{Creating a Thumbnail Preview for Your Theme}{chapter.455}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {455.1}{\ignorespaces Your theme thumbnail is displayed with the rest of the available themes.}}{1324}{figure.455.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {455.2}{\ignorespaces Your theme thumbnail is displayed with the rest of the available themes.}}{1324}{figure.455.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {455.1}Related Topics}{1325}{section.455.1}\protected@file@percent }
\newlabel{related-topics-54}{{455.1}{1325}{Related Topics}{section.455.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {456}Creating Color Schemes for Your Theme}{1327}{chapter.456}\protected@file@percent }
\newlabel{creating-color-schemes-for-your-theme}{{456}{1327}{Creating Color Schemes for Your Theme}{chapter.456}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {456.1}{\ignorespaces Color schemes give administrators some choices for your theme's look.}}{1327}{figure.456.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {456.1}Related Topics}{1329}{section.456.1}\protected@file@percent }
\newlabel{related-topics-55}{{456.1}{1329}{Related Topics}{section.456.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {457}Making Configurable Theme Settings}{1331}{chapter.457}\protected@file@percent }
\newlabel{making-configurable-theme-settings}{{457}{1331}{Making Configurable Theme Settings}{chapter.457}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {457.1}{\ignorespaces Here are examples of configurable settings for the site Admin.}}{1332}{figure.457.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {457.1}Related Topics}{1333}{section.457.1}\protected@file@percent }
\newlabel{related-topics-56}{{457.1}{1333}{Related Topics}{section.457.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {458}Using Font Awesome and Glyph Icons in Your Theme}{1335}{chapter.458}\protected@file@percent }
\newlabel{using-font-awesome-and-glyph-icons-in-your-theme}{{458}{1335}{Using Font Awesome and Glyph Icons in Your Theme}{chapter.458}{}}
\@writefile{toc}{\contentsline {section}{\numberline {458.1}Disabling Enabling Global Font Awesome and Glyphicons in Portal}{1335}{section.458.1}\protected@file@percent }
\newlabel{disabling-enabling-global-font-awesome-and-glyphicons-in-portal}{{458.1}{1335}{Disabling Enabling Global Font Awesome and Glyphicons in Portal}{section.458.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {458.2}Including Font Awesome and Glyphicons in Your Theme}{1335}{section.458.2}\protected@file@percent }
\newlabel{including-font-awesome-and-glyphicons-in-your-theme}{{458.2}{1335}{Including Font Awesome and Glyphicons in Your Theme}{section.458.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {458.3}Related Topics}{1336}{section.458.3}\protected@file@percent }
\newlabel{related-topics-57}{{458.3}{1336}{Related Topics}{section.458.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {459}Extending Themes}{1337}{chapter.459}\protected@file@percent }
\newlabel{extending-themes}{{459}{1337}{Extending Themes}{chapter.459}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {460}Installing a Themelet in Your Theme}{1339}{chapter.460}\protected@file@percent }
\newlabel{installing-a-themelet-in-your-theme}{{460}{1339}{Installing a Themelet in Your Theme}{chapter.460}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {460.1}{\ignorespaces You can extend your theme using globally installed npm modules or published npm modules.}}{1339}{figure.460.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {460.1}Related Topics}{1340}{section.460.1}\protected@file@percent }
\newlabel{related-topics-58}{{460.1}{1340}{Related Topics}{section.460.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {461}Injecting Additional Context Variables and Functionality into Your Theme Templates}{1341}{chapter.461}\protected@file@percent }
\newlabel{injecting-additional-context-variables-and-functionality-into-your-theme-templates}{{461}{1341}{Injecting Additional Context Variables and Functionality into Your Theme Templates}{chapter.461}{}}
\@writefile{toc}{\contentsline {section}{\numberline {461.1}Related Topics}{1342}{section.461.1}\protected@file@percent }
\newlabel{related-topics-59}{{461.1}{1342}{Related Topics}{section.461.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {462}Packaging Independent UI Resources for Your Site}{1343}{chapter.462}\protected@file@percent }
\newlabel{packaging-independent-ui-resources-for-your-site}{{462}{1343}{Packaging Independent UI Resources for Your Site}{chapter.462}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {462.1}{\ignorespaces The Control Menu, Product Menu, and Simulation Panel are packaged as Theme Contributor modules.}}{1344}{figure.462.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {462.1}Related Topics}{1344}{section.462.1}\protected@file@percent }
\newlabel{related-topics-60}{{462.1}{1344}{Related Topics}{section.462.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {463}Changing Your Base Theme}{1345}{chapter.463}\protected@file@percent }
\newlabel{changing-your-base-theme}{{463}{1345}{Changing Your Base Theme}{chapter.463}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {463.1}{\ignorespaces Run the \texttt {gulp\ extend} task to change your base theme.}}{1345}{figure.463.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {463.1}Related Topics}{1346}{section.463.1}\protected@file@percent }
\newlabel{related-topics-61}{{463.1}{1346}{Related Topics}{section.463.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {464}Copying an Existing Theme's Files}{1347}{chapter.464}\protected@file@percent }
\newlabel{copying-an-existing-themes-files}{{464}{1347}{Copying an Existing Theme's Files}{chapter.464}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {464.1}{\ignorespaces Run the \texttt {gulp\ kickstart} task to copy a theme's files into your own theme.}}{1347}{figure.464.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {464.1}Related Topics}{1348}{section.464.1}\protected@file@percent }
\newlabel{related-topics-62}{{464.1}{1348}{Related Topics}{section.464.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {465}Listing Your Theme's Extensions}{1349}{chapter.465}\protected@file@percent }
\newlabel{listing-your-themes-extensions}{{465}{1349}{Listing Your Theme's Extensions}{chapter.465}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {465.1}{\ignorespaces Run the \texttt {gulp\ status} task to list your theme's current extensions.}}{1349}{figure.465.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {465.1}Related Topics}{1349}{section.465.1}\protected@file@percent }
\newlabel{related-topics-63}{{465.1}{1349}{Related Topics}{section.465.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {466}Overwriting and Extending Liferay Theme Tasks}{1351}{chapter.466}\protected@file@percent }
\newlabel{overwriting-and-extending-liferay-theme-tasks}{{466}{1351}{Overwriting and Extending Liferay Theme Tasks}{chapter.466}{}}
\@writefile{toc}{\contentsline {section}{\numberline {466.1}Related Topics}{1353}{section.466.1}\protected@file@percent }
\newlabel{related-topics-64}{{466.1}{1353}{Related Topics}{section.466.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {467}Clay CSS and Themes}{1355}{chapter.467}\protected@file@percent }
\newlabel{clay-css-and-themes}{{467}{1355}{Clay CSS and Themes}{chapter.467}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {468}Customizing Atlas and Clay Base Themes in Liferay DXP}{1357}{chapter.468}\protected@file@percent }
\newlabel{customizing-atlas-and-clay-base-themes-in-liferay-dxp}{{468}{1357}{Customizing Atlas and Clay Base Themes in Liferay DXP}{chapter.468}{}}
\@writefile{toc}{\contentsline {section}{\numberline {468.1}Related Topics}{1358}{section.468.1}\protected@file@percent }
\newlabel{related-topics-65}{{468.1}{1358}{Related Topics}{section.468.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {469}Integrating Third Party Themes with Clay}{1359}{chapter.469}\protected@file@percent }
\newlabel{integrating-third-party-themes-with-clay}{{469}{1359}{Integrating Third Party Themes with Clay}{chapter.469}{}}
\@writefile{toc}{\contentsline {section}{\numberline {469.1}Related Topics}{1360}{section.469.1}\protected@file@percent }
\newlabel{related-topics-66}{{469.1}{1360}{Related Topics}{section.469.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {470}Using Clay Icons in a Theme}{1361}{chapter.470}\protected@file@percent }
\newlabel{using-clay-icons-in-a-theme}{{470}{1361}{Using Clay Icons in a Theme}{chapter.470}{}}
\@writefile{toc}{\contentsline {section}{\numberline {470.1}Related Topics}{1361}{section.470.1}\protected@file@percent }
\newlabel{related-topics-67}{{470.1}{1361}{Related Topics}{section.470.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {471}Using Clay Mixins in Your Theme}{1363}{chapter.471}\protected@file@percent }
\newlabel{using-clay-mixins-in-your-theme}{{471}{1363}{Using Clay Mixins in Your Theme}{chapter.471}{}}
\@writefile{toc}{\contentsline {section}{\numberline {471.1}Related Topics}{1363}{section.471.1}\protected@file@percent }
\newlabel{related-topics-68}{{471.1}{1363}{Related Topics}{section.471.1}{}}
\gdef \LT@xvi {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {472}Theming Portlets}{1365}{chapter.472}\protected@file@percent }
\newlabel{theming-portlets}{{472}{1365}{Theming Portlets}{chapter.472}{}}
\@writefile{toc}{\contentsline {section}{\numberline {472.1}Portlet Decorators}{1366}{section.472.1}\protected@file@percent }
\newlabel{portlet-decorators}{{472.1}{1366}{Portlet Decorators}{section.472.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {472.1}{\ignorespaces The Classic theme's Decorate Application Decorator wraps the portlet in a white box.}}{1367}{figure.472.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {472.2}{\ignorespaces The Classic theme's Borderless Application Decorator displays the application's custom title.}}{1368}{figure.472.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {472.3}{\ignorespaces The Classic theme's Barebone Application Decorator displays only the application's content.}}{1369}{figure.472.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {473}Embedding Portlets in Themes}{1371}{chapter.473}\protected@file@percent }
\newlabel{embedding-portlets-in-themes}{{473}{1371}{Embedding Portlets in Themes}{chapter.473}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {474}Embedding Portlets in Themes by Entity Type and Action}{1373}{chapter.474}\protected@file@percent }
\newlabel{embedding-portlets-in-themes-by-entity-type-and-action}{{474}{1373}{Embedding Portlets in Themes by Entity Type and Action}{chapter.474}{}}
\@writefile{toc}{\contentsline {section}{\numberline {474.1}Related Topics}{1375}{section.474.1}\protected@file@percent }
\newlabel{related-topics-69}{{474.1}{1375}{Related Topics}{section.474.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {475}Embedding a Portlet by Portlet Name}{1377}{chapter.475}\protected@file@percent }
\newlabel{embedding-a-portlet-by-portlet-name}{{475}{1377}{Embedding a Portlet by Portlet Name}{chapter.475}{}}
\@writefile{toc}{\contentsline {section}{\numberline {475.1}Related Topics}{1377}{section.475.1}\protected@file@percent }
\newlabel{related-topics-70}{{475.1}{1377}{Related Topics}{section.475.1}{}}
\gdef \LT@xvii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {476}Setting Default Preferences for an Embedded Portlet}{1379}{chapter.476}\protected@file@percent }
\newlabel{setting-default-preferences-for-an-embedded-portlet}{{476}{1379}{Setting Default Preferences for an Embedded Portlet}{chapter.476}{}}
\@writefile{toc}{\contentsline {section}{\numberline {476.1}Related Topics}{1380}{section.476.1}\protected@file@percent }
\newlabel{related-topics-71}{{476.1}{1380}{Related Topics}{section.476.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {477}Importing Resources with a Theme}{1381}{chapter.477}\protected@file@percent }
\newlabel{importing-resources-with-a-theme}{{477}{1381}{Importing Resources with a Theme}{chapter.477}{}}
\@writefile{toc}{\contentsline {section}{\numberline {477.1}Organizing Your Resources}{1381}{section.477.1}\protected@file@percent }
\newlabel{organizing-your-resources}{{477.1}{1381}{Organizing Your Resources}{section.477.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {478}Creating a Sitemap for the Resources Importer}{1383}{chapter.478}\protected@file@percent }
\newlabel{creating-a-sitemap-for-the-resources-importer}{{478}{1383}{Creating a Sitemap for the Resources Importer}{chapter.478}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {479}Defining Layout Templates and Pages in a Sitemap}{1387}{chapter.479}\protected@file@percent }
\newlabel{defining-layout-templates-and-pages-in-a-sitemap}{{479}{1387}{Defining Layout Templates and Pages in a Sitemap}{chapter.479}{}}
\@writefile{toc}{\contentsline {section}{\numberline {479.1}Related Topics}{1389}{section.479.1}\protected@file@percent }
\newlabel{related-topics-72}{{479.1}{1389}{Related Topics}{section.479.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {480}Defining Portlets in a Sitemap}{1391}{chapter.480}\protected@file@percent }
\newlabel{defining-portlets-in-a-sitemap}{{480}{1391}{Defining Portlets in a Sitemap}{chapter.480}{}}
\@writefile{toc}{\contentsline {section}{\numberline {480.1}Related Topics}{1393}{section.480.1}\protected@file@percent }
\newlabel{related-topics-73}{{480.1}{1393}{Related Topics}{section.480.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {481}Retrieving Portlet IDs with the Gogo Shell}{1395}{chapter.481}\protected@file@percent }
\newlabel{retrieving-portlet-ids-with-the-gogo-shell}{{481}{1395}{Retrieving Portlet IDs with the Gogo Shell}{chapter.481}{}}
\@writefile{toc}{\contentsline {section}{\numberline {481.1}Related Topics}{1395}{section.481.1}\protected@file@percent }
\newlabel{related-topics-74}{{481.1}{1395}{Related Topics}{section.481.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {481.1}{\ignorespaces Portlet IDs can be found via the Gogo Shell.}}{1396}{figure.481.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {482}Preparing and Organizing Web Content for the Resources Importer}{1397}{chapter.482}\protected@file@percent }
\newlabel{preparing-and-organizing-web-content-for-the-resources-importer}{{482}{1397}{Preparing and Organizing Web Content for the Resources Importer}{chapter.482}{}}
\@writefile{toc}{\contentsline {section}{\numberline {482.1}Related Topics}{1399}{section.482.1}\protected@file@percent }
\newlabel{related-topics-75}{{482.1}{1399}{Related Topics}{section.482.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {483}Defining Assets for the Resources Importer}{1401}{chapter.483}\protected@file@percent }
\newlabel{defining-assets-for-the-resources-importer}{{483}{1401}{Defining Assets for the Resources Importer}{chapter.483}{}}
\@writefile{toc}{\contentsline {section}{\numberline {483.1}Related Topics}{1402}{section.483.1}\protected@file@percent }
\newlabel{related-topics-76}{{483.1}{1402}{Related Topics}{section.483.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {484}Specifying Where to Import Your Theme's Resources}{1403}{chapter.484}\protected@file@percent }
\newlabel{specifying-where-to-import-your-themes-resources}{{484}{1403}{Specifying Where to Import Your Theme's Resources}{chapter.484}{}}
\@writefile{toc}{\contentsline {section}{\numberline {484.1}Related Topics}{1404}{section.484.1}\protected@file@percent }
\newlabel{related-topics-77}{{484.1}{1404}{Related Topics}{section.484.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {485}Archiving Site Resources}{1405}{chapter.485}\protected@file@percent }
\newlabel{archiving-site-resources}{{485}{1405}{Archiving Site Resources}{chapter.485}{}}
\@writefile{toc}{\contentsline {section}{\numberline {485.1}Related Topics}{1405}{section.485.1}\protected@file@percent }
\newlabel{related-topics-78}{{485.1}{1405}{Related Topics}{section.485.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {486}Troubleshooting Themes}{1407}{chapter.486}\protected@file@percent }
\newlabel{troubleshooting-themes}{{486}{1407}{Troubleshooting Themes}{chapter.486}{}}
\newlabel{osgi-headers-in-themes}{{486}{1407}{Troubleshooting Themes}{section*.14}{}}
\newlabel{developer-mode}{{486}{1407}{Troubleshooting Themes}{section*.15}{}}
\newlabel{default-theme-returned}{{486}{1407}{Troubleshooting Themes}{section*.16}{}}
\newlabel{rtl-no-flip}{{486}{1407}{Troubleshooting Themes}{section*.17}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {487}Layout Templates}{1409}{chapter.487}\protected@file@percent }
\newlabel{layout-templates}{{487}{1409}{Layout Templates}{chapter.487}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {487.1}{\ignorespaces There are many default layout templates to choose from.}}{1409}{figure.487.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {488}Creating Custom Layout Template Thumbnail Previews}{1411}{chapter.488}\protected@file@percent }
\newlabel{creating-custom-layout-template-thumbnail-previews}{{488}{1411}{Creating Custom Layout Template Thumbnail Previews}{chapter.488}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {488.1}{\ignorespaces A thumbnail preview displays the layout's design to the user.}}{1411}{figure.488.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {488.1}Related topics}{1412}{section.488.1}\protected@file@percent }
\newlabel{related-topics-79}{{488.1}{1412}{Related topics}{section.488.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {489}Including Layout Templates with a Theme}{1413}{chapter.489}\protected@file@percent }
\newlabel{including-layout-templates-with-a-theme}{{489}{1413}{Including Layout Templates with a Theme}{chapter.489}{}}
\@writefile{toc}{\contentsline {section}{\numberline {489.1}Related topics}{1414}{section.489.1}\protected@file@percent }
\newlabel{related-topics-80}{{489.1}{1414}{Related topics}{section.489.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {490}Creating and Bundling JavaScript Widgets with JavaScript Tooling}{1415}{chapter.490}\protected@file@percent }
\newlabel{creating-and-bundling-javascript-widgets-with-javascript-tooling}{{490}{1415}{Creating and Bundling JavaScript Widgets with JavaScript Tooling}{chapter.490}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {490.1}{\ignorespaces The JS Portlet Extender lets you use pure JavaScript tooling to write widgets.}}{1416}{figure.490.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {491}Configuring System Settings and Instance Settings for Your JavaScript Widget}{1417}{chapter.491}\protected@file@percent }
\newlabel{configuring-system-settings-and-instance-settings-for-your-javascript-widget}{{491}{1417}{Configuring System Settings and Instance Settings for Your JavaScript Widget}{chapter.491}{}}
\@writefile{toc}{\contentsline {section}{\numberline {491.1}Related Topics}{1418}{section.491.1}\protected@file@percent }
\newlabel{related-topics-81}{{491.1}{1418}{Related Topics}{section.491.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {492}Localizing Your Widget}{1419}{chapter.492}\protected@file@percent }
\newlabel{localizing-your-widget}{{492}{1419}{Localizing Your Widget}{chapter.492}{}}
\@writefile{toc}{\contentsline {section}{\numberline {492.1}Related Topics}{1420}{section.492.1}\protected@file@percent }
\newlabel{related-topics-82}{{492.1}{1420}{Related Topics}{section.492.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {493}Using Translation Features in Your Widget}{1421}{chapter.493}\protected@file@percent }
\newlabel{using-translation-features-in-your-widget}{{493}{1421}{Using Translation Features in Your Widget}{chapter.493}{}}
\@writefile{toc}{\contentsline {section}{\numberline {493.1}Related Topics}{1421}{section.493.1}\protected@file@percent }
\newlabel{related-topics-83}{{493.1}{1421}{Related Topics}{section.493.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {494}Configuring Portlet Properties for Your Widget}{1423}{chapter.494}\protected@file@percent }
\newlabel{configuring-portlet-properties-for-your-widget}{{494}{1423}{Configuring Portlet Properties for Your Widget}{chapter.494}{}}
\@writefile{toc}{\contentsline {section}{\numberline {494.1}Related Topics}{1423}{section.494.1}\protected@file@percent }
\newlabel{related-topics-84}{{494.1}{1423}{Related Topics}{section.494.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {495}JavaScript Module Loaders}{1425}{chapter.495}\protected@file@percent }
\newlabel{javascript-module-loaders}{{495}{1425}{JavaScript Module Loaders}{chapter.495}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {496}Loading AMD Modules in Liferay}{1427}{chapter.496}\protected@file@percent }
\newlabel{loading-amd-modules-in-liferay}{{496}{1427}{Loading AMD Modules in Liferay}{chapter.496}{}}
\@writefile{toc}{\contentsline {section}{\numberline {496.1}Related Topics}{1428}{section.496.1}\protected@file@percent }
\newlabel{related-topics-85}{{496.1}{1428}{Related Topics}{section.496.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {497}Using External JavaScript Libraries}{1429}{chapter.497}\protected@file@percent }
\newlabel{using-external-javascript-libraries}{{497}{1429}{Using External JavaScript Libraries}{chapter.497}{}}
\@writefile{toc}{\contentsline {section}{\numberline {497.1}Related Topics}{1430}{section.497.1}\protected@file@percent }
\newlabel{related-topics-86}{{497.1}{1430}{Related Topics}{section.497.1}{}}
\gdef \LT@xviii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {498}Loading Modules with AUI Script}{1431}{chapter.498}\protected@file@percent }
\newlabel{loading-modules-with-aui-script}{{498}{1431}{Loading Modules with AUI Script}{chapter.498}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {499}Loading AlloyUI Modules with AUI Script}{1433}{chapter.499}\protected@file@percent }
\newlabel{loading-alloyui-modules-with-aui-script}{{499}{1433}{Loading AlloyUI Modules with AUI Script}{chapter.499}{}}
\@writefile{toc}{\contentsline {section}{\numberline {499.1}Related Topics}{1434}{section.499.1}\protected@file@percent }
\newlabel{related-topics-87}{{499.1}{1434}{Related Topics}{section.499.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {500}Loading ES2015 and Metal.js Modules with AUI Script}{1435}{chapter.500}\protected@file@percent }
\newlabel{loading-es2015-and-metal.js-modules-with-aui-script}{{500}{1435}{Loading ES2015 and Metal.js Modules with AUI Script}{chapter.500}{}}
\@writefile{toc}{\contentsline {section}{\numberline {500.1}Related Topics}{1436}{section.500.1}\protected@file@percent }
\newlabel{related-topics-88}{{500.1}{1436}{Related Topics}{section.500.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {501}Loading AUI, ES2015, and Metal.js Modules Together with AUI Script}{1437}{chapter.501}\protected@file@percent }
\newlabel{loading-aui-es2015-and-metal.js-modules-together-with-aui-script}{{501}{1437}{Loading AUI, ES2015, and Metal.js Modules Together with AUI Script}{chapter.501}{}}
\@writefile{toc}{\contentsline {section}{\numberline {501.1}Related Topics}{1437}{section.501.1}\protected@file@percent }
\newlabel{related-topics-89}{{501.1}{1437}{Related Topics}{section.501.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {502}Loading Bundled npm Modules in Your Portlets}{1439}{chapter.502}\protected@file@percent }
\newlabel{loading-bundled-npm-modules-in-your-portlets}{{502}{1439}{Loading Bundled npm Modules in Your Portlets}{chapter.502}{}}
\@writefile{toc}{\contentsline {section}{\numberline {502.1}Related Topics}{1440}{section.502.1}\protected@file@percent }
\newlabel{related-topics-90}{{502.1}{1440}{Related Topics}{section.502.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {503}The Info Framework}{1441}{chapter.503}\protected@file@percent }
\newlabel{the-info-framework}{{503}{1441}{The Info Framework}{chapter.503}{}}
\@writefile{toc}{\contentsline {section}{\numberline {503.1}Using the Info Framework}{1441}{section.503.1}\protected@file@percent }
\newlabel{using-the-info-framework}{{503.1}{1441}{Using the Info Framework}{section.503.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {503.2}List Providers}{1442}{section.503.2}\protected@file@percent }
\newlabel{list-providers}{{503.2}{1442}{List Providers}{section.503.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {503.3}Item Renderers}{1442}{section.503.3}\protected@file@percent }
\newlabel{item-renderers}{{503.3}{1442}{Item Renderers}{section.503.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {504}Creating an Information List Provider}{1443}{chapter.504}\protected@file@percent }
\newlabel{creating-an-information-list-provider}{{504}{1443}{Creating an Information List Provider}{chapter.504}{}}
\@writefile{toc}{\contentsline {section}{\numberline {504.1}Next steps}{1445}{section.504.1}\protected@file@percent }
\newlabel{next-steps}{{504.1}{1445}{Next steps}{section.504.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {505}Custom rendering of information with \texttt {InfoItemRenderer}}{1447}{chapter.505}\protected@file@percent }
\newlabel{custom-rendering-of-information-with-infoitemrenderer}{{505}{1447}{\texorpdfstring {Custom rendering of information with \texttt {InfoItemRenderer}}{Custom rendering of information with InfoItemRenderer}}{chapter.505}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {506}Using Providers with Custom Applications}{1449}{chapter.506}\protected@file@percent }
\newlabel{using-providers-with-custom-applications}{{506}{1449}{Using Providers with Custom Applications}{chapter.506}{}}
\@writefile{toc}{\contentsline {section}{\numberline {506.1}Leveraging renderers from a custom application}{1449}{section.506.1}\protected@file@percent }
\newlabel{leveraging-renderers-from-a-custom-application}{{506.1}{1449}{Leveraging renderers from a custom application}{section.506.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {507}Liferay Forms}{1451}{chapter.507}\protected@file@percent }
\newlabel{liferay-forms}{{507}{1451}{Liferay Forms}{chapter.507}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {508}Form Serialization with the DDM IO API}{1453}{chapter.508}\protected@file@percent }
\newlabel{form-serialization-with-the-ddm-io-api}{{508}{1453}{Form Serialization with the DDM IO API}{chapter.508}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {509}Serializing Forms}{1455}{chapter.509}\protected@file@percent }
\newlabel{serializing-forms}{{509}{1455}{Serializing Forms}{chapter.509}{}}
\@writefile{toc}{\contentsline {section}{\numberline {509.1}Calling the Serializer}{1456}{section.509.1}\protected@file@percent }
\newlabel{calling-the-serializer}{{509.1}{1456}{Calling the Serializer}{section.509.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {510}Localization}{1457}{chapter.510}\protected@file@percent }
\newlabel{localization}{{510}{1457}{Localization}{chapter.510}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {511}Localizing Your Application}{1459}{chapter.511}\protected@file@percent }
\newlabel{localizing-your-application}{{511}{1459}{Localizing Your Application}{chapter.511}{}}
\@writefile{toc}{\contentsline {section}{\numberline {511.1}Related Topics}{1461}{section.511.1}\protected@file@percent }
\newlabel{related-topics-91}{{511.1}{1461}{Related Topics}{section.511.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {512}Using Liferay's Localization Settings}{1463}{chapter.512}\protected@file@percent }
\newlabel{using-liferays-localization-settings}{{512}{1463}{Using Liferay's Localization Settings}{chapter.512}{}}
\@writefile{toc}{\contentsline {section}{\numberline {512.1}Localizing User Names}{1464}{section.512.1}\protected@file@percent }
\newlabel{localizing-user-names}{{512.1}{1464}{Localizing User Names}{section.512.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {512.1}{\ignorespaces The user name settings impact the appearance of user information and forms.}}{1465}{figure.512.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {512.2}{\ignorespaces The Spanish user name settings omit the suffix and middle name fields.}}{1466}{figure.512.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {512.2}Identifying User Initials}{1466}{section.512.2}\protected@file@percent }
\newlabel{identifying-user-initials}{{512.2}{1466}{Identifying User Initials}{section.512.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {512.3}{\ignorespaces The user's initials are displayed for their avatar by default.}}{1466}{figure.512.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {512.3}Right to Left or Left to Right?}{1466}{section.512.3}\protected@file@percent }
\newlabel{right-to-left-or-left-to-right}{{512.3}{1466}{Right to Left or Left to Right?}{section.512.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {512.4}Related Topics}{1467}{section.512.4}\protected@file@percent }
\newlabel{related-topics-92}{{512.4}{1467}{Related Topics}{section.512.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {513}Creating a Language Module}{1469}{chapter.513}\protected@file@percent }
\newlabel{creating-a-language-module}{{513}{1469}{Creating a Language Module}{chapter.513}{}}
\@writefile{toc}{\contentsline {section}{\numberline {513.1}Related Topics}{1470}{section.513.1}\protected@file@percent }
\newlabel{related-topics-93}{{513.1}{1470}{Related Topics}{section.513.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {514}Using a Language Module}{1471}{chapter.514}\protected@file@percent }
\newlabel{using-a-language-module}{{514}{1471}{Using a Language Module}{chapter.514}{}}
\@writefile{toc}{\contentsline {section}{\numberline {514.1}Using a Language Module from a Module}{1471}{section.514.1}\protected@file@percent }
\newlabel{using-a-language-module-from-a-module}{{514.1}{1471}{Using a Language Module from a Module}{section.514.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {514.2}Using a Language Module from a Traditional Plugin}{1472}{section.514.2}\protected@file@percent }
\newlabel{using-a-language-module-from-a-traditional-plugin}{{514.2}{1472}{Using a Language Module from a Traditional Plugin}{section.514.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {514.3}Related Topics}{1473}{section.514.3}\protected@file@percent }
\newlabel{related-topics-94}{{514.3}{1473}{Related Topics}{section.514.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {515}Automatically Generating Translations}{1475}{chapter.515}\protected@file@percent }
\newlabel{automatically-generating-translations}{{515}{1475}{Automatically Generating Translations}{chapter.515}{}}
\@writefile{toc}{\contentsline {section}{\numberline {515.1}Configuring the Language Builder Plugin}{1475}{section.515.1}\protected@file@percent }
\newlabel{configuring-the-language-builder-plugin}{{515.1}{1475}{Configuring the Language Builder Plugin}{section.515.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {515.2}Running Language Builder}{1476}{section.515.2}\protected@file@percent }
\newlabel{running-language-builder}{{515.2}{1476}{Running Language Builder}{section.515.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {515.3}Translating Language Keys Automatically}{1477}{section.515.3}\protected@file@percent }
\newlabel{translating-language-keys-automatically}{{515.3}{1477}{Translating Language Keys Automatically}{section.515.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {515.4}Related Topics}{1478}{section.515.4}\protected@file@percent }
\newlabel{related-topics-95}{{515.4}{1478}{Related Topics}{section.515.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {516}Portlets}{1479}{chapter.516}\protected@file@percent }
\newlabel{portlets}{{516}{1479}{Portlets}{chapter.516}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {516.1}{\ignorespaces You can place multiple portlets on a single page.}}{1480}{figure.516.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {516.1}Related Topics}{1481}{section.516.1}\protected@file@percent }
\newlabel{related-topics-96}{{516.1}{1481}{Related Topics}{section.516.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {517}Using JavaScript in Your Portlets}{1483}{chapter.517}\protected@file@percent }
\newlabel{using-javascript-in-your-portlets}{{517}{1483}{Using JavaScript in Your Portlets}{chapter.517}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {518}Using ES2015 Modules in your Portlet}{1485}{chapter.518}\protected@file@percent }
\newlabel{using-es2015-modules-in-your-portlet}{{518}{1485}{Using ES2015 Modules in your Portlet}{chapter.518}{}}
\@writefile{toc}{\contentsline {section}{\numberline {518.1}Related Topics}{1486}{section.518.1}\protected@file@percent }
\newlabel{related-topics-97}{{518.1}{1486}{Related Topics}{section.518.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {519}Using npm in Your Portlets}{1487}{chapter.519}\protected@file@percent }
\newlabel{using-npm-in-your-portlets}{{519}{1487}{Using npm in Your Portlets}{chapter.519}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {520}Formatting Your npm Modules for AMD}{1489}{chapter.520}\protected@file@percent }
\newlabel{formatting-your-npm-modules-for-amd}{{520}{1489}{Formatting Your npm Modules for AMD}{chapter.520}{}}
\@writefile{toc}{\contentsline {section}{\numberline {520.1}Related Topics}{1490}{section.520.1}\protected@file@percent }
\newlabel{related-topics-98}{{520.1}{1490}{Related Topics}{section.520.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {521}Migrating a liferay-npm-bundler Project from 1.x to 2.x}{1491}{chapter.521}\protected@file@percent }
\newlabel{migrating-a-liferay-npm-bundler-project-from-1.x-to-2.x}{{521}{1491}{Migrating a liferay-npm-bundler Project from 1.x to 2.x}{chapter.521}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {522}Migrating a Plain JavaScript, Billboard JS, JQuery, Metal JS, React, or Vue JS Project to Use Bundler 2.x}{1493}{chapter.522}\protected@file@percent }
\newlabel{migrating-a-plain-javascript-billboard-js-jquery-metal-js-react-or-vue-js-project-to-use-bundler-2.x}{{522}{1493}{Migrating a Plain JavaScript, Billboard JS, JQuery, Metal JS, React, or Vue JS Project to Use Bundler 2.x}{chapter.522}{}}
\@writefile{toc}{\contentsline {section}{\numberline {522.1}Related Topics}{1494}{section.522.1}\protected@file@percent }
\newlabel{related-topics-99}{{522.1}{1494}{Related Topics}{section.522.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {523}Migrating an Angular Project to Use Bundler 2.x}{1495}{chapter.523}\protected@file@percent }
\newlabel{migrating-an-angular-project-to-use-bundler-2.x}{{523}{1495}{Migrating an Angular Project to Use Bundler 2.x}{chapter.523}{}}
\@writefile{toc}{\contentsline {section}{\numberline {523.1}Related Topics}{1496}{section.523.1}\protected@file@percent }
\newlabel{related-topics-100}{{523.1}{1496}{Related Topics}{section.523.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {524}Migrating Your Project to Use liferay-npm-bundler's New Mode}{1497}{chapter.524}\protected@file@percent }
\newlabel{migrating-your-project-to-use-liferay-npm-bundlers-new-mode}{{524}{1497}{Migrating Your Project to Use liferay-npm-bundler's New Mode}{chapter.524}{}}
\@writefile{toc}{\contentsline {section}{\numberline {524.1}Related Topics}{1498}{section.524.1}\protected@file@percent }
\newlabel{related-topics-101}{{524.1}{1498}{Related Topics}{section.524.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {525}Creating Custom Loaders for the liferay-npm-bundler}{1499}{chapter.525}\protected@file@percent }
\newlabel{creating-custom-loaders-for-the-liferay-npm-bundler}{{525}{1499}{Creating Custom Loaders for the liferay-npm-bundler}{chapter.525}{}}
\@writefile{toc}{\contentsline {section}{\numberline {525.1}Related Topics}{1501}{section.525.1}\protected@file@percent }
\newlabel{related-topics-102}{{525.1}{1501}{Related Topics}{section.525.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {526}Using the NPMResolver API in Your Portlets}{1503}{chapter.526}\protected@file@percent }
\newlabel{using-the-npmresolver-api-in-your-portlets}{{526}{1503}{Using the NPMResolver API in Your Portlets}{chapter.526}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {527}Referencing an npm Module's Package to Improve Code Maintenance}{1505}{chapter.527}\protected@file@percent }
\newlabel{referencing-an-npm-modules-package-to-improve-code-maintenance}{{527}{1505}{Referencing an npm Module's Package to Improve Code Maintenance}{chapter.527}{}}
\@writefile{toc}{\contentsline {section}{\numberline {527.1}Related Topics}{1506}{section.527.1}\protected@file@percent }
\newlabel{related-topics-103}{{527.1}{1506}{Related Topics}{section.527.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {528}Obtaining an OSGi bundle's Dependency npm Package Descriptors}{1507}{chapter.528}\protected@file@percent }
\newlabel{obtaining-an-osgi-bundles-dependency-npm-package-descriptors}{{528}{1507}{Obtaining an OSGi bundle's Dependency npm Package Descriptors}{chapter.528}{}}
\@writefile{toc}{\contentsline {section}{\numberline {528.1}Related Topics}{1508}{section.528.1}\protected@file@percent }
\newlabel{related-topics-104}{{528.1}{1508}{Related Topics}{section.528.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {529}Automatic Single Page Applications}{1509}{chapter.529}\protected@file@percent }
\newlabel{automatic-single-page-applications}{{529}{1509}{Automatic Single Page Applications}{chapter.529}{}}
\@writefile{toc}{\contentsline {section}{\numberline {529.1}The Benefits of SPAs}{1509}{section.529.1}\protected@file@percent }
\newlabel{the-benefits-of-spas}{{529.1}{1509}{The Benefits of SPAs}{section.529.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {529.2}What is SennaJS?}{1510}{section.529.2}\protected@file@percent }
\newlabel{what-is-sennajs}{{529.2}{1510}{What is SennaJS?}{section.529.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {530}Configuring SPA System Settings}{1511}{chapter.530}\protected@file@percent }
\newlabel{configuring-spa-system-settings}{{530}{1511}{Configuring SPA System Settings}{chapter.530}{}}
\@writefile{toc}{\contentsline {section}{\numberline {530.1}Related Topics}{1511}{section.530.1}\protected@file@percent }
\newlabel{related-topics-105}{{530.1}{1511}{Related Topics}{section.530.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {531}Disabling SPA}{1513}{chapter.531}\protected@file@percent }
\newlabel{disabling-spa}{{531}{1513}{Disabling SPA}{chapter.531}{}}
\@writefile{toc}{\contentsline {section}{\numberline {531.1}Disabling SPA across an Instance}{1513}{section.531.1}\protected@file@percent }
\newlabel{disabling-spa-across-an-instance}{{531.1}{1513}{Disabling SPA across an Instance}{section.531.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {531.2}Disabling SPA for a Portlet}{1513}{section.531.2}\protected@file@percent }
\newlabel{disabling-spa-for-a-portlet}{{531.2}{1513}{Disabling SPA for a Portlet}{section.531.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {531.3}Disabling SPA for an Individual Element}{1514}{section.531.3}\protected@file@percent }
\newlabel{disabling-spa-for-an-individual-element}{{531.3}{1514}{Disabling SPA for an Individual Element}{section.531.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {531.4}Related Topics}{1514}{section.531.4}\protected@file@percent }
\newlabel{related-topics-106}{{531.4}{1514}{Related Topics}{section.531.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {532}Specifying How Resources Are Loaded During Navigation}{1515}{chapter.532}\protected@file@percent }
\newlabel{specifying-how-resources-are-loaded-during-navigation}{{532}{1515}{Specifying How Resources Are Loaded During Navigation}{chapter.532}{}}
\@writefile{toc}{\contentsline {section}{\numberline {532.1}Related Topics}{1515}{section.532.1}\protected@file@percent }
\newlabel{related-topics-107}{{532.1}{1515}{Related Topics}{section.532.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {533}Detaching Global Listeners}{1517}{chapter.533}\protected@file@percent }
\newlabel{detaching-global-listeners}{{533}{1517}{Detaching Global Listeners}{chapter.533}{}}
\@writefile{toc}{\contentsline {section}{\numberline {533.1}Related Topics}{1518}{section.533.1}\protected@file@percent }
\newlabel{related-topics-108}{{533.1}{1518}{Related Topics}{section.533.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {534}Applying Clay Styles to your App}{1519}{chapter.534}\protected@file@percent }
\newlabel{applying-clay-styles-to-your-app}{{534}{1519}{Applying Clay Styles to your App}{chapter.534}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {535}Applying Clay Patterns to Navigation}{1521}{chapter.535}\protected@file@percent }
\newlabel{applying-clay-patterns-to-navigation}{{535}{1521}{Applying Clay Patterns to Navigation}{chapter.535}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {535.1}{\ignorespaces The navigation bar should be light for apps on the live site.}}{1522}{figure.535.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {535.2}{\ignorespaces The navigation bar should be dark (inverted) in admin apps.}}{1522}{figure.535.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {535.1}Related topics}{1523}{section.535.1}\protected@file@percent }
\newlabel{related-topics-109}{{535.1}{1523}{Related topics}{section.535.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {536}Implementing the Management Toolbar}{1525}{chapter.536}\protected@file@percent }
\newlabel{implementing-the-management-toolbar}{{536}{1525}{Implementing the Management Toolbar}{chapter.536}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {537}Implementing the View Types}{1527}{chapter.537}\protected@file@percent }
\newlabel{implementing-the-view-types}{{537}{1527}{Implementing the View Types}{chapter.537}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {538}Implementing the Card View}{1529}{chapter.538}\protected@file@percent }
\newlabel{implementing-the-card-view}{{538}{1529}{Implementing the Card View}{chapter.538}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {538.1}{\ignorespaces The Management Toolbar's card view gives a quick summary of the content's description and status.}}{1529}{figure.538.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {538.1}Related Topics}{1530}{section.538.1}\protected@file@percent }
\newlabel{related-topics-110}{{538.1}{1530}{Related Topics}{section.538.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {539}Implementing the List View}{1531}{chapter.539}\protected@file@percent }
\newlabel{implementing-the-list-view}{{539}{1531}{Implementing the List View}{chapter.539}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {539.1}{\ignorespaces The Management Toolbar's list view gives the content's full description.}}{1531}{figure.539.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {539.1}Related Topics}{1532}{section.539.1}\protected@file@percent }
\newlabel{related-topics-111}{{539.1}{1532}{Related Topics}{section.539.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {540}Implementing the Table View}{1533}{chapter.540}\protected@file@percent }
\newlabel{implementing-the-table-view}{{540}{1533}{Implementing the Table View}{chapter.540}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {540.1}{\ignorespaces The Management Toolbar's table view list the content's information in individual columns.}}{1533}{figure.540.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {540.1}Related Topics}{1534}{section.540.1}\protected@file@percent }
\newlabel{related-topics-112}{{540.1}{1534}{Related Topics}{section.540.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {541}Updating the Search Iterator}{1535}{chapter.541}\protected@file@percent }
\newlabel{updating-the-search-iterator}{{541}{1535}{Updating the Search Iterator}{chapter.541}{}}
\@writefile{toc}{\contentsline {section}{\numberline {541.1}Related Topics}{1535}{section.541.1}\protected@file@percent }
\newlabel{related-topics-113}{{541.1}{1535}{Related Topics}{section.541.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {542}Filtering and Sorting Items with the Management Toolbar}{1537}{chapter.542}\protected@file@percent }
\newlabel{filtering-and-sorting-items-with-the-management-toolbar}{{542}{1537}{Filtering and Sorting Items with the Management Toolbar}{chapter.542}{}}
\@writefile{toc}{\contentsline {section}{\numberline {542.1}Related Topics}{1538}{section.542.1}\protected@file@percent }
\newlabel{related-topics-114}{{542.1}{1538}{Related Topics}{section.542.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {543}Configuring Your Application's Title and Back Link}{1539}{chapter.543}\protected@file@percent }
\newlabel{configuring-your-applications-title-and-back-link}{{543}{1539}{Configuring Your Application's Title and Back Link}{chapter.543}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {543.1}{\ignorespaces Adding a new blog entry displays the portlet title at the top, along with a back link.}}{1539}{figure.543.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {543.1}Related Topics}{1540}{section.543.1}\protected@file@percent }
\newlabel{related-topics-115}{{543.1}{1540}{Related Topics}{section.543.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {544}Applying the Add Button Pattern}{1541}{chapter.544}\protected@file@percent }
\newlabel{applying-the-add-button-pattern}{{544}{1541}{Applying the Add Button Pattern}{chapter.544}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {544.1}{\ignorespaces The add button pattern consists of an \texttt {add-menu} tag and at least one \texttt {add-menu-item} tag.}}{1542}{figure.544.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {544.1}Related Topics}{1542}{section.544.1}\protected@file@percent }
\newlabel{related-topics-116}{{544.1}{1542}{Related Topics}{section.544.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {545}Configuring Your Admin App's Actions Menu}{1543}{chapter.545}\protected@file@percent }
\newlabel{configuring-your-admin-apps-actions-menu}{{545}{1543}{Configuring Your Admin App's Actions Menu}{chapter.545}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {545.1}{\ignorespaces The upper right ellipsis menu contains most of the actions for the app.}}{1543}{figure.545.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {545.1}Related Topics}{1546}{section.545.1}\protected@file@percent }
\newlabel{related-topics-117}{{545.1}{1546}{Related Topics}{section.545.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {546}Setting Empty Results Messages}{1547}{chapter.546}\protected@file@percent }
\newlabel{setting-empty-results-messages}{{546}{1547}{Setting Empty Results Messages}{chapter.546}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {546.1}{\ignorespaces This is a still frame from the Web Content portlet's empty results animation.}}{1547}{figure.546.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {546.2}{\ignorespaces If you can use the add button to add entities to the app, use the empty state animation.}}{1548}{figure.546.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {546.3}{\ignorespaces If you can use the add button to add entities to the app, use the search state animation.}}{1548}{figure.546.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {546.4}{\ignorespaces If you can use the add button to add entities to the app, use the success state animation.}}{1549}{figure.546.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {546.1}Related Topics}{1549}{section.546.1}\protected@file@percent }
\newlabel{related-topics-118}{{546.1}{1549}{Related Topics}{section.546.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {547}Search}{1551}{chapter.547}\protected@file@percent }
\newlabel{search}{{547}{1551}{Search}{chapter.547}{}}
\@writefile{toc}{\contentsline {section}{\numberline {547.1}Basic Search Concepts}{1551}{section.547.1}\protected@file@percent }
\newlabel{basic-search-concepts}{{547.1}{1551}{Basic Search Concepts}{section.547.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {547.2}Mapping Definitions}{1551}{section.547.2}\protected@file@percent }
\newlabel{mapping-definitions}{{547.2}{1551}{Mapping Definitions}{section.547.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {547.3}Liferay Search Infrastructure}{1552}{section.547.3}\protected@file@percent }
\newlabel{liferay-search-infrastructure}{{547.3}{1552}{Liferay Search Infrastructure}{section.547.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {548}Aggregations}{1553}{chapter.548}\protected@file@percent }
\newlabel{aggregations}{{548}{1553}{Aggregations}{chapter.548}{}}
\@writefile{toc}{\contentsline {section}{\numberline {548.1}Using Aggregations}{1554}{section.548.1}\protected@file@percent }
\newlabel{using-aggregations}{{548.1}{1554}{Using Aggregations}{section.548.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {548.2}External References}{1554}{section.548.2}\protected@file@percent }
\newlabel{external-references}{{548.2}{1554}{External References}{section.548.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {548.3}Search Engine Connector Support}{1554}{section.548.3}\protected@file@percent }
\newlabel{search-engine-connector-support}{{548.3}{1554}{Search Engine Connector Support}{section.548.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {548.4}New/Related APIs}{1554}{section.548.4}\protected@file@percent }
\newlabel{newrelated-apis}{{548.4}{1554}{New/Related APIs}{section.548.4}{}}
\gdef \LT@xix {\LT@entry
{1}{120.02339pt}\LT@entry
{1}{240.04678pt}\LT@entry
{1}{109.68483pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {549}Creating Aggregations}{1557}{chapter.549}\protected@file@percent }
\newlabel{creating-aggregations}{{549}{1557}{Creating Aggregations}{chapter.549}{}}
\@writefile{toc}{\contentsline {section}{\numberline {549.1}Instantiate and Construct the Aggregation}{1557}{section.549.1}\protected@file@percent }
\newlabel{instantiate-and-construct-the-aggregation}{{549.1}{1557}{Instantiate and Construct the Aggregation}{section.549.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {549.2}Build the Search Query}{1557}{section.549.2}\protected@file@percent }
\newlabel{build-the-search-query}{{549.2}{1557}{Build the Search Query}{section.549.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {549.3}Execute the Search Query}{1558}{section.549.3}\protected@file@percent }
\newlabel{execute-the-search-query}{{549.3}{1558}{Execute the Search Query}{section.549.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {549.4}Process the response}{1558}{section.549.4}\protected@file@percent }
\newlabel{process-the-response}{{549.4}{1558}{Process the response}{section.549.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {550}Statistical Aggregations}{1559}{chapter.550}\protected@file@percent }
\newlabel{statistical-aggregations}{{550}{1559}{Statistical Aggregations}{chapter.550}{}}
\@writefile{toc}{\contentsline {section}{\numberline {550.1}\texttt {StatsRequest}}{1559}{section.550.1}\protected@file@percent }
\newlabel{statsrequest}{{550.1}{1559}{\texorpdfstring {\texttt {StatsRequest}}{StatsRequest}}{section.550.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {550.2}\texttt {StatsResponse}}{1560}{section.550.2}\protected@file@percent }
\newlabel{statsresponse}{{550.2}{1560}{\texorpdfstring {\texttt {StatsResponse}}{StatsResponse}}{section.550.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {550.3}Using the Legacy \texttt {Stats} Object}{1561}{section.550.3}\protected@file@percent }
\newlabel{using-the-legacy-stats-object}{{550.3}{1561}{\texorpdfstring {Using the Legacy \texttt {Stats} Object}{Using the Legacy Stats Object}}{section.550.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {550.4}External References}{1561}{section.550.4}\protected@file@percent }
\newlabel{external-references-1}{{550.4}{1561}{External References}{section.550.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {550.5}Search Engine Connector Support}{1561}{section.550.5}\protected@file@percent }
\newlabel{search-engine-connector-support-1}{{550.5}{1561}{Search Engine Connector Support}{section.550.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {550.6}New/Related APIs}{1561}{section.550.6}\protected@file@percent }
\newlabel{newrelated-apis-1}{{550.6}{1561}{New/Related APIs}{section.550.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {550.7}Deprecated APIs}{1562}{section.550.7}\protected@file@percent }
\newlabel{deprecated-apis}{{550.7}{1562}{Deprecated APIs}{section.550.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {551}Model Entity Indexing Framework}{1563}{chapter.551}\protected@file@percent }
\newlabel{model-entity-indexing-framework}{{551}{1563}{Model Entity Indexing Framework}{chapter.551}{}}
\@writefile{toc}{\contentsline {section}{\numberline {551.1}Search and Indexing Overview}{1563}{section.551.1}\protected@file@percent }
\newlabel{search-and-indexing-overview}{{551.1}{1563}{Search and Indexing Overview}{section.551.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {551.2}Mapping the Composite Search and Indexing Framework to \texttt {Indexer}/\texttt {BaseIndexer} Code}{1563}{section.551.2}\protected@file@percent }
\newlabel{mapping-the-composite-search-and-indexing-framework-to-indexerbaseindexer-code}{{551.2}{1563}{\texorpdfstring {Mapping the Composite Search and Indexing Framework to \texttt {Indexer}/\texttt {BaseIndexer} Code}{Mapping the Composite Search and Indexing Framework to Indexer/BaseIndexer Code}}{section.551.2}{}}
\gdef \LT@xx {\LT@entry
{1}{177.9261pt}\LT@entry
{1}{183.9261pt}\LT@entry
{1}{107.90279pt}}
\@writefile{toc}{\contentsline {section}{\numberline {551.3}Permissions-Aware Searching and Indexing}{1564}{section.551.3}\protected@file@percent }
\newlabel{permissions-aware-searching-and-indexing}{{551.3}{1564}{Permissions-Aware Searching and Indexing}{section.551.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {551.4}Annotating Service Methods to Trigger Indexing}{1564}{section.551.4}\protected@file@percent }
\newlabel{annotating-service-methods-to-trigger-indexing}{{551.4}{1564}{Annotating Service Methods to Trigger Indexing}{section.551.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {551.5}Search and Localization: a Cheat Sheet}{1565}{section.551.5}\protected@file@percent }
\newlabel{search-and-localization-a-cheat-sheet}{{551.5}{1565}{Search and Localization: a Cheat Sheet}{section.551.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {552}Indexing Model Entities}{1567}{chapter.552}\protected@file@percent }
\newlabel{indexing-model-entities}{{552}{1567}{Indexing Model Entities}{chapter.552}{}}
\@writefile{toc}{\contentsline {section}{\numberline {552.1}Contributing Model Entity Fields to the Index}{1567}{section.552.1}\protected@file@percent }
\newlabel{contributing-model-entity-fields-to-the-index}{{552.1}{1567}{Contributing Model Entity Fields to the Index}{section.552.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {552.2}Configure Re-Indexing and Batch Indexing Behavior}{1568}{section.552.2}\protected@file@percent }
\newlabel{configure-re-indexing-and-batch-indexing-behavior}{{552.2}{1568}{Configure Re-Indexing and Batch Indexing Behavior}{section.552.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {552.3}Contribute Fields to Every Document}{1570}{section.552.3}\protected@file@percent }
\newlabel{contribute-fields-to-every-document}{{552.3}{1570}{Contribute Fields to Every Document}{section.552.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {553}Searching the Index for Model Entities}{1571}{chapter.553}\protected@file@percent }
\newlabel{searching-the-index-for-model-entities}{{553}{1571}{Searching the Index for Model Entities}{chapter.553}{}}
\@writefile{toc}{\contentsline {section}{\numberline {553.1}Adding your Model Entity's Terms to the Query}{1571}{section.553.1}\protected@file@percent }
\newlabel{adding-your-model-entitys-terms-to-the-query}{{553.1}{1571}{Adding your Model Entity's Terms to the Query}{section.553.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {553.2}Contributing Query Clauses to Every Search}{1572}{section.553.2}\protected@file@percent }
\newlabel{contributing-query-clauses-to-every-search}{{553.2}{1572}{Contributing Query Clauses to Every Search}{section.553.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {553.3}Pre-Filtering}{1572}{section.553.3}\protected@file@percent }
\newlabel{pre-filtering}{{553.3}{1572}{Pre-Filtering}{section.553.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {554}Returning Results}{1575}{chapter.554}\protected@file@percent }
\newlabel{returning-results}{{554}{1575}{Returning Results}{chapter.554}{}}
\@writefile{toc}{\contentsline {section}{\numberline {554.1}Creating a Results Summary}{1575}{section.554.1}\protected@file@percent }
\newlabel{creating-a-results-summary}{{554.1}{1575}{Creating a Results Summary}{section.554.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {554.2}Controlling the Visibility of Model Entities}{1576}{section.554.2}\protected@file@percent }
\newlabel{controlling-the-visibility-of-model-entities}{{554.2}{1576}{Controlling the Visibility of Model Entities}{section.554.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {555}Search Service Registration}{1577}{chapter.555}\protected@file@percent }
\newlabel{search-service-registration}{{555}{1577}{Search Service Registration}{chapter.555}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {556}Search Queries and Filters}{1579}{chapter.556}\protected@file@percent }
\newlabel{search-queries-and-filters}{{556}{1579}{Search Queries and Filters}{chapter.556}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.1}Queries and Filters in Liferay's Search API}{1579}{section.556.1}\protected@file@percent }
\newlabel{queries-and-filters-in-liferays-search-api}{{556.1}{1579}{Queries and Filters in Liferay's Search API}{section.556.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.2}Supported Query Types}{1580}{section.556.2}\protected@file@percent }
\newlabel{supported-query-types}{{556.2}{1580}{Supported Query Types}{section.556.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.3}Full Text Queries}{1580}{section.556.3}\protected@file@percent }
\newlabel{full-text-queries}{{556.3}{1580}{Full Text Queries}{section.556.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.4}Term Queries}{1580}{section.556.4}\protected@file@percent }
\newlabel{term-queries}{{556.4}{1580}{Term Queries}{section.556.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.5}Compound Queries}{1581}{section.556.5}\protected@file@percent }
\newlabel{compound-queries}{{556.5}{1581}{Compound Queries}{section.556.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.6}Joining Queries}{1581}{section.556.6}\protected@file@percent }
\newlabel{joining-queries}{{556.6}{1581}{Joining Queries}{section.556.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.7}Geo Queries}{1581}{section.556.7}\protected@file@percent }
\newlabel{geo-queries}{{556.7}{1581}{Geo Queries}{section.556.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.8}Specialized Queries}{1581}{section.556.8}\protected@file@percent }
\newlabel{specialized-queries}{{556.8}{1581}{Specialized Queries}{section.556.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.9}Other Queries}{1582}{section.556.9}\protected@file@percent }
\newlabel{other-queries}{{556.9}{1582}{Other Queries}{section.556.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.10}Using Queries}{1582}{section.556.10}\protected@file@percent }
\newlabel{using-queries}{{556.10}{1582}{Using Queries}{section.556.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.11}Search Queries in Liferay's Code}{1582}{section.556.11}\protected@file@percent }
\newlabel{search-queries-in-liferays-code}{{556.11}{1582}{Search Queries in Liferay's Code}{section.556.11}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.12}External References}{1582}{section.556.12}\protected@file@percent }
\newlabel{external-references-2}{{556.12}{1582}{External References}{section.556.12}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.13}Search Engine Connector Support}{1583}{section.556.13}\protected@file@percent }
\newlabel{search-engine-connector-support-2}{{556.13}{1583}{Search Engine Connector Support}{section.556.13}{}}
\@writefile{toc}{\contentsline {section}{\numberline {556.14}New/Related APIs}{1583}{section.556.14}\protected@file@percent }
\newlabel{newrelated-apis-2}{{556.14}{1583}{New/Related APIs}{section.556.14}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {557}Building Search Queries and Filters}{1585}{chapter.557}\protected@file@percent }
\newlabel{building-search-queries-and-filters}{{557}{1585}{Building Search Queries and Filters}{chapter.557}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.1}Queries}{1585}{section.557.1}\protected@file@percent }
\newlabel{queries}{{557.1}{1585}{Queries}{section.557.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.2}Declare Gradle Dependencies}{1585}{section.557.2}\protected@file@percent }
\newlabel{declare-gradle-dependencies}{{557.2}{1585}{Declare Gradle Dependencies}{section.557.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.3}Reference the Search Services}{1585}{section.557.3}\protected@file@percent }
\newlabel{reference-the-search-services}{{557.3}{1585}{Reference the Search Services}{section.557.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.4}Build the Search Query}{1586}{section.557.4}\protected@file@percent }
\newlabel{build-the-search-query-1}{{557.4}{1586}{Build the Search Query}{section.557.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.5}Build the Search Request}{1586}{section.557.5}\protected@file@percent }
\newlabel{build-the-search-request}{{557.5}{1586}{Build the Search Request}{section.557.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.6}Execute the Search Request}{1587}{section.557.6}\protected@file@percent }
\newlabel{execute-the-search-request}{{557.6}{1587}{Execute the Search Request}{section.557.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.7}Process the Search Response}{1587}{section.557.7}\protected@file@percent }
\newlabel{process-the-search-response}{{557.7}{1587}{Process the Search Response}{section.557.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.8}Search Insights: Request and Response Strings}{1588}{section.557.8}\protected@file@percent }
\newlabel{search-insights-request-and-response-strings}{{557.8}{1588}{Search Insights: Request and Response Strings}{section.557.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.9}Queries Example}{1589}{section.557.9}\protected@file@percent }
\newlabel{queries-example}{{557.9}{1589}{Queries Example}{section.557.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.10}Filters}{1590}{section.557.10}\protected@file@percent }
\newlabel{filters}{{557.10}{1590}{Filters}{section.557.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.11}Legacy Filters in Legacy Search Calls}{1591}{section.557.11}\protected@file@percent }
\newlabel{legacy-filters-in-legacy-search-calls}{{557.11}{1591}{Legacy Filters in Legacy Search Calls}{section.557.11}{}}
\@writefile{toc}{\contentsline {section}{\numberline {557.12}Discovering Indexed Fields}{1591}{section.557.12}\protected@file@percent }
\newlabel{discovering-indexed-fields}{{557.12}{1591}{Discovering Indexed Fields}{section.557.12}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {558}Segmentation and Personalization}{1593}{chapter.558}\protected@file@percent }
\newlabel{segmentation-and-personalization}{{558}{1593}{Segmentation and Personalization}{chapter.558}{}}
\@writefile{toc}{\contentsline {section}{\numberline {558.1}Managing segments}{1593}{section.558.1}\protected@file@percent }
\newlabel{managing-segments}{{558.1}{1593}{Managing segments}{section.558.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {558.2}Extending Segment Criteria}{1594}{section.558.2}\protected@file@percent }
\newlabel{extending-segment-criteria}{{558.2}{1594}{Extending Segment Criteria}{section.558.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {558.3}\texttt {RequestContextContributor}}{1594}{section.558.3}\protected@file@percent }
\newlabel{requestcontextcontributor}{{558.3}{1594}{\texorpdfstring {\texttt {RequestContextContributor}}{RequestContextContributor}}{section.558.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {558.4}\texttt {SegmentsCriteriaContributor}}{1594}{section.558.4}\protected@file@percent }
\newlabel{segmentscriteriacontributor}{{558.4}{1594}{\texorpdfstring {\texttt {SegmentsCriteriaContributor}}{SegmentsCriteriaContributor}}{section.558.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {558.1}{\ignorespaces Learn more about a \texttt {RequestContextContributor} by viewing how it's used.}}{1595}{figure.558.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {558.2}{\ignorespaces Learn more about a \texttt {SegmentsCriteriaContributor} by viewing how it's used.}}{1596}{figure.558.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {559}Segment Management}{1597}{chapter.559}\protected@file@percent }
\newlabel{segment-management}{{559}{1597}{Segment Management}{chapter.559}{}}
\@writefile{toc}{\contentsline {section}{\numberline {559.1}Defining a Segment}{1597}{section.559.1}\protected@file@percent }
\newlabel{defining-a-segment}{{559.1}{1597}{Defining a Segment}{section.559.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {559.2}Manual Segment Member Assignments}{1597}{section.559.2}\protected@file@percent }
\newlabel{manual-segment-member-assignments}{{559.2}{1597}{Manual Segment Member Assignments}{section.559.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {559.3}Retrieving Segments}{1598}{section.559.3}\protected@file@percent }
\newlabel{retrieving-segments}{{559.3}{1598}{Retrieving Segments}{section.559.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {559.4}Retrieving segment members}{1598}{section.559.4}\protected@file@percent }
\newlabel{retrieving-segment-members}{{559.4}{1598}{Retrieving segment members}{section.559.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {560}Creating a Request Context Contributor}{1599}{chapter.560}\protected@file@percent }
\newlabel{creating-a-request-context-contributor}{{560}{1599}{Creating a Request Context Contributor}{chapter.560}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {560.1}{\ignorespaces The sample field appears.}}{1601}{figure.560.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {561}Creating a Segment Criteria Contributor}{1603}{chapter.561}\protected@file@percent }
\newlabel{creating-a-segment-criteria-contributor}{{561}{1603}{Creating a Segment Criteria Contributor}{chapter.561}{}}
\@writefile{toc}{\contentsline {section}{\numberline {561.1}Creating the Entity Model}{1603}{section.561.1}\protected@file@percent }
\newlabel{creating-the-entity-model}{{561.1}{1603}{Creating the Entity Model}{section.561.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {561.2}Creating the \texttt {ODataRetriever}}{1604}{section.561.2}\protected@file@percent }
\newlabel{creating-the-odataretriever}{{561.2}{1604}{\texorpdfstring {Creating the \texttt {ODataRetriever}}{Creating the ODataRetriever}}{section.561.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {561.3}Creating the \texttt {SegmentsCriteriaContributor}}{1606}{section.561.3}\protected@file@percent }
\newlabel{creating-the-segmentscriteriacontributor}{{561.3}{1606}{\texorpdfstring {Creating the \texttt {SegmentsCriteriaContributor}}{Creating the SegmentsCriteriaContributor}}{section.561.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {561.1}{\ignorespaces The sample field appears.}}{1608}{figure.561.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {562}ServiceContext}{1609}{chapter.562}\protected@file@percent }
\newlabel{servicecontext}{{562}{1609}{ServiceContext}{chapter.562}{}}
\@writefile{toc}{\contentsline {section}{\numberline {562.1}Service Context Fields}{1609}{section.562.1}\protected@file@percent }
\newlabel{service-context-fields}{{562.1}{1609}{Service Context Fields}{section.562.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {562.2}Creating and Populating a Service Context}{1611}{section.562.2}\protected@file@percent }
\newlabel{creating-and-populating-a-service-context}{{562.2}{1611}{Creating and Populating a Service Context}{section.562.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {562.3}Creating and Populating a Service Context in JavaScript}{1611}{section.562.3}\protected@file@percent }
\newlabel{creating-and-populating-a-service-context-in-javascript}{{562.3}{1611}{Creating and Populating a Service Context in JavaScript}{section.562.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {562.4}Accessing Service Context Data}{1612}{section.562.4}\protected@file@percent }
\newlabel{accessing-service-context-data}{{562.4}{1612}{Accessing Service Context Data}{section.562.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {562.1}{\ignorespaces When you invoke a service from Liferay's JSON web services page, you can view the result of your service invocation as well as example code for invoking the service via JavaScript, curl, or URL.}}{1613}{figure.562.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {562.5}Related Topics}{1615}{section.562.5}\protected@file@percent }
\newlabel{related-topics-119}{{562.5}{1615}{Related Topics}{section.562.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {563}Injecting Service Components into Integration Tests}{1617}{chapter.563}\protected@file@percent }
\newlabel{injecting-service-components-into-integration-tests}{{563}{1617}{Injecting Service Components into Integration Tests}{chapter.563}{}}
\@writefile{toc}{\contentsline {section}{\numberline {563.1}Related Topics}{1618}{section.563.1}\protected@file@percent }
\newlabel{related-topics-120}{{563.1}{1618}{Related Topics}{section.563.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {564}Upgrade Processes}{1619}{chapter.564}\protected@file@percent }
\newlabel{upgrade-processes}{{564}{1619}{Upgrade Processes}{chapter.564}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {564.1}{\ignorespaces In a registrator class, the developer specifies a registration for each schema version upgrade. The upgrade steps handle the database updates.}}{1621}{figure.564.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {565}Creating Upgrade Processes for Modules}{1623}{chapter.565}\protected@file@percent }
\newlabel{creating-upgrade-processes-for-modules}{{565}{1623}{Creating Upgrade Processes for Modules}{chapter.565}{}}
\@writefile{toc}{\contentsline {section}{\numberline {565.1}Related Topics}{1627}{section.565.1}\protected@file@percent }
\newlabel{related-topics-121}{{565.1}{1627}{Related Topics}{section.565.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {566}Upgrade Processes for Former Service Builder Plugins}{1629}{chapter.566}\protected@file@percent }
\newlabel{upgrade-processes-for-former-service-builder-plugins}{{566}{1629}{Upgrade Processes for Former Service Builder Plugins}{chapter.566}{}}
\@writefile{toc}{\contentsline {section}{\numberline {566.1}Related Topics}{1632}{section.566.1}\protected@file@percent }
\newlabel{related-topics-122}{{566.1}{1632}{Related Topics}{section.566.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {567}Upgrading Data Schemas in Development}{1633}{chapter.567}\protected@file@percent }
\newlabel{upgrading-data-schemas-in-development}{{567}{1633}{Upgrading Data Schemas in Development}{chapter.567}{}}
\gdef \LT@xxi {\LT@entry
{1}{244.03456pt}\LT@entry
{1}{225.72044pt}}
\@writefile{toc}{\contentsline {section}{\numberline {567.1}Related Topics}{1634}{section.567.1}\protected@file@percent }
\newlabel{related-topics-123}{{567.1}{1634}{Related Topics}{section.567.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {568}Managing User-Associated Data Stored by Custom Applications}{1635}{chapter.568}\protected@file@percent }
\newlabel{managing-user-associated-data-stored-by-custom-applications}{{568}{1635}{Managing User-Associated Data Stored by Custom Applications}{chapter.568}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {569}Adding the UAD Framework to a Service Builder Application}{1637}{chapter.569}\protected@file@percent }
\newlabel{adding-the-uad-framework-to-a-service-builder-application}{{569}{1637}{Adding the UAD Framework to a Service Builder Application}{chapter.569}{}}
\@writefile{toc}{\contentsline {section}{\numberline {569.1}Update the Service Module}{1637}{section.569.1}\protected@file@percent }
\newlabel{update-the-service-module}{{569.1}{1637}{Update the Service Module}{section.569.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {569.2}Include Dependencies}{1637}{section.569.2}\protected@file@percent }
\newlabel{include-dependencies}{{569.2}{1637}{Include Dependencies}{section.569.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {569.3}Choose the Fields to Anonymize}{1637}{section.569.3}\protected@file@percent }
\newlabel{choose-the-fields-to-anonymize}{{569.3}{1637}{Choose the Fields to Anonymize}{section.569.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {569.4}Update the UAD Module}{1638}{section.569.4}\protected@file@percent }
\newlabel{update-the-uad-module}{{569.4}{1638}{Update the UAD Module}{section.569.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {569.5}Include Dependencies}{1638}{section.569.5}\protected@file@percent }
\newlabel{include-dependencies-1}{{569.5}{1638}{Include Dependencies}{section.569.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {569.6}Provide Your App's Name to the UI}{1638}{section.569.6}\protected@file@percent }
\newlabel{provide-your-apps-name-to-the-ui}{{569.6}{1638}{Provide Your App's Name to the UI}{section.569.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {570}Enhancing the Data Erasure UI}{1639}{chapter.570}\protected@file@percent }
\newlabel{enhancing-the-data-erasure-ui}{{570}{1639}{Enhancing the Data Erasure UI}{chapter.570}{}}
\@writefile{toc}{\contentsline {section}{\numberline {570.1}Filtering and Searching in the Data Erasure UI}{1639}{section.570.1}\protected@file@percent }
\newlabel{filtering-and-searching-in-the-data-erasure-ui}{{570.1}{1639}{Filtering and Searching in the Data Erasure UI}{section.570.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {570.1}{\ignorespaces Items in the Personal Data Erasure screen can be filtered by scope.}}{1640}{figure.570.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {570.2}Hierarchy Display}{1640}{section.570.2}\protected@file@percent }
\newlabel{hierarchy-display}{{570.2}{1640}{Hierarchy Display}{section.570.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {570.3}UAD Hierarchy Declaration}{1640}{section.570.3}\protected@file@percent }
\newlabel{uad-hierarchy-declaration}{{570.3}{1640}{UAD Hierarchy Declaration}{section.570.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {570.2}{\ignorespaces Hierarchical representation of nested entities is useful for administrators reviewing User data for possible deletion.}}{1641}{figure.570.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {570.4}Add methods to UADDisplay}{1641}{section.570.4}\protected@file@percent }
\newlabel{add-methods-to-uaddisplay}{{570.4}{1641}{Add methods to UADDisplay}{section.570.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {571}Filtering and Searching UAD-Marked Entities}{1645}{chapter.571}\protected@file@percent }
\newlabel{filtering-and-searching-uad-marked-entities}{{571}{1645}{Filtering and Searching UAD-Marked Entities}{chapter.571}{}}
\@writefile{toc}{\contentsline {section}{\numberline {571.1}Filtering}{1645}{section.571.1}\protected@file@percent }
\newlabel{filtering}{{571.1}{1645}{Filtering}{section.571.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {571.2}Search}{1645}{section.571.2}\protected@file@percent }
\newlabel{search-1}{{571.2}{1645}{Search}{section.571.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {572}Web Experience Management}{1649}{chapter.572}\protected@file@percent }
\newlabel{web-experience-management}{{572}{1649}{Web Experience Management}{chapter.572}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {573}Page Fragments}{1651}{chapter.573}\protected@file@percent }
\newlabel{page-fragments}{{573}{1651}{Page Fragments}{chapter.573}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.1}Developing Page Fragments}{1651}{section.573.1}\protected@file@percent }
\newlabel{developing-page-fragments}{{573.1}{1651}{Developing Page Fragments}{section.573.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.2}Making a Fragment Configurable}{1651}{section.573.2}\protected@file@percent }
\newlabel{making-a-fragment-configurable}{{573.2}{1651}{Making a Fragment Configurable}{section.573.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.3}Fragments CLI}{1652}{section.573.3}\protected@file@percent }
\newlabel{fragments-cli}{{573.3}{1652}{Fragments CLI}{section.573.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.4}Contributed Collections}{1652}{section.573.4}\protected@file@percent }
\newlabel{contributed-collections}{{573.4}{1652}{Contributed Collections}{section.573.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.5}Fragment Specific Tags}{1652}{section.573.5}\protected@file@percent }
\newlabel{fragment-specific-tags}{{573.5}{1652}{Fragment Specific Tags}{section.573.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.6}Recommendations and Best Practices}{1653}{section.573.6}\protected@file@percent }
\newlabel{recommendations-and-best-practices}{{573.6}{1653}{Recommendations and Best Practices}{section.573.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.7}CSS}{1653}{section.573.7}\protected@file@percent }
\newlabel{css}{{573.7}{1653}{CSS}{section.573.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {573.8}JavaScript}{1653}{section.573.8}\protected@file@percent }
\newlabel{javascript}{{573.8}{1653}{JavaScript}{section.573.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {574}Developing Fragments}{1655}{chapter.574}\protected@file@percent }
\newlabel{developing-fragments}{{574}{1655}{Developing Fragments}{chapter.574}{}}
\@writefile{toc}{\contentsline {section}{\numberline {574.1}Creating a Section}{1655}{section.574.1}\protected@file@percent }
\newlabel{creating-a-section}{{574.1}{1655}{Creating a Section}{section.574.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {574.1}{\ignorespaces The Fragment editor provides autocomplete for Liferay Fragment specific tags.}}{1656}{figure.574.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {574.2}{\ignorespaces The Fragment editor with HTML and CSS code and a live preview.}}{1657}{figure.574.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {574.2}Creating a Component}{1657}{section.574.2}\protected@file@percent }
\newlabel{creating-a-component}{{574.2}{1657}{Creating a Component}{section.574.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {575}Making a Fragment Configurable}{1659}{chapter.575}\protected@file@percent }
\newlabel{making-a-fragment-configurable-1}{{575}{1659}{Making a Fragment Configurable}{chapter.575}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {575.1}{\ignorespaces Switch from the Code tab to the Configuration tab to create your configuration logic.}}{1659}{figure.575.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {575.2}{\ignorespaces You can click your Fragment to view its configuration options.}}{1661}{figure.575.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {576}Managing Fragments and Collections}{1663}{chapter.576}\protected@file@percent }
\newlabel{managing-fragments-and-collections}{{576}{1663}{Managing Fragments and Collections}{chapter.576}{}}
\@writefile{toc}{\contentsline {section}{\numberline {576.1}Collections Management Menu}{1663}{section.576.1}\protected@file@percent }
\newlabel{collections-management-menu}{{576.1}{1663}{Collections Management Menu}{section.576.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {576.2}Fragment Management Menu}{1663}{section.576.2}\protected@file@percent }
\newlabel{fragment-management-menu}{{576.2}{1663}{Fragment Management Menu}{section.576.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {576.3}Propagating Fragment Changes Automatically}{1664}{section.576.3}\protected@file@percent }
\newlabel{propagating-fragment-changes-automatically}{{576.3}{1664}{Propagating Fragment Changes Automatically}{section.576.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {576.1}{\ignorespaces Once Fragment propagation is enabled, developers can automatically propagate Fragment changes to all pages using them.}}{1664}{figure.576.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {576.2}{\ignorespaces You're notified when automatic propagation is enabled.}}{1665}{figure.576.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {577}Developing A Fragment Using Desktop Tools}{1667}{chapter.577}\protected@file@percent }
\newlabel{developing-a-fragment-using-desktop-tools}{{577}{1667}{Developing A Fragment Using Desktop Tools}{chapter.577}{}}
\@writefile{toc}{\contentsline {section}{\numberline {577.1}Collection Format}{1667}{section.577.1}\protected@file@percent }
\newlabel{collection-format}{{577.1}{1667}{Collection Format}{section.577.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {577.2}Fragment CLI}{1668}{section.577.2}\protected@file@percent }
\newlabel{fragment-cli}{{577.2}{1668}{Fragment CLI}{section.577.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {577.3}Creating Collections}{1668}{section.577.3}\protected@file@percent }
\newlabel{creating-collections}{{577.3}{1668}{Creating Collections}{section.577.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {577.4}Creating Fragments}{1669}{section.577.4}\protected@file@percent }
\newlabel{creating-fragments}{{577.4}{1669}{Creating Fragments}{section.577.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {577.5}Importing and Exporting Fragments}{1669}{section.577.5}\protected@file@percent }
\newlabel{importing-and-exporting-fragments}{{577.5}{1669}{Importing and Exporting Fragments}{section.577.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {578}Creating a Contributed Fragment Collection}{1671}{chapter.578}\protected@file@percent }
\newlabel{creating-a-contributed-fragment-collection}{{578}{1671}{Creating a Contributed Fragment Collection}{chapter.578}{}}
\@writefile{toc}{\contentsline {section}{\numberline {578.1}Create a Module}{1671}{section.578.1}\protected@file@percent }
\newlabel{create-a-module}{{578.1}{1671}{Create a Module}{section.578.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {578.2}Create the Java Class}{1671}{section.578.2}\protected@file@percent }
\newlabel{create-the-java-class}{{578.2}{1671}{Create the Java Class}{section.578.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {578.3}Create the Resources}{1672}{section.578.3}\protected@file@percent }
\newlabel{create-the-resources}{{578.3}{1672}{Create the Resources}{section.578.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {578.4}Configuring the Metadata}{1673}{section.578.4}\protected@file@percent }
\newlabel{configuring-the-metadata}{{578.4}{1673}{Configuring the Metadata}{section.578.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {578.5}Providing Thumbnail Images}{1673}{section.578.5}\protected@file@percent }
\newlabel{providing-thumbnail-images}{{578.5}{1673}{Providing Thumbnail Images}{section.578.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {578.6}Providing Language Keys}{1673}{section.578.6}\protected@file@percent }
\newlabel{providing-language-keys}{{578.6}{1673}{Providing Language Keys}{section.578.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {578.7}Deploy the Contributed Fragment Collection}{1674}{section.578.7}\protected@file@percent }
\newlabel{deploy-the-contributed-fragment-collection}{{578.7}{1674}{Deploy the Contributed Fragment Collection}{section.578.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {579}Including Default Resources in Fragments}{1675}{chapter.579}\protected@file@percent }
\newlabel{including-default-resources-in-fragments}{{579}{1675}{Including Default Resources in Fragments}{chapter.579}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {579.1}{\ignorespaces Any Fragment from the Fragment Collection has access to the uploaded resources.}}{1676}{figure.579.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {580}Supporting Custom Content Types in Content and Display Pages}{1677}{chapter.580}\protected@file@percent }
\newlabel{supporting-custom-content-types-in-content-and-display-pages}{{580}{1677}{Supporting Custom Content Types in Content and Display Pages}{chapter.580}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {581}Mapping a Content Type to a Page}{1679}{chapter.581}\protected@file@percent }
\newlabel{mapping-a-content-type-to-a-page}{{581}{1679}{Mapping a Content Type to a Page}{chapter.581}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {581.1}{\ignorespaces After creating the \texttt {*InfoDisplayContributor} class, you can create Display Page Templates and map them to your custom model.}}{1680}{figure.581.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {582}Specifying the Fields of a Custom Content Type}{1681}{chapter.582}\protected@file@percent }
\newlabel{specifying-the-fields-of-a-custom-content-type}{{582}{1681}{Specifying the Fields of a Custom Content Type}{chapter.582}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {582.1}{\ignorespaces After creating the \texttt {*InfoDisplayContributorField} class, your custom content type has a new field to map.}}{1683}{figure.582.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {583}Providing Friendly URLs for a Custom Content Type}{1685}{chapter.583}\protected@file@percent }
\newlabel{providing-friendly-urls-for-a-custom-content-type}{{583}{1685}{Providing Friendly URLs for a Custom Content Type}{chapter.583}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {584}Integrating Display Pages into Content Creation}{1691}{chapter.584}\protected@file@percent }
\newlabel{integrating-display-pages-into-content-creation}{{584}{1691}{Integrating Display Pages into Content Creation}{chapter.584}{}}
\@writefile{toc}{\contentsline {section}{\numberline {584.1}Display Page Taglib Example}{1691}{section.584.1}\protected@file@percent }
\newlabel{display-page-taglib-example}{{584.1}{1691}{Display Page Taglib Example}{section.584.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {584.1}{\ignorespaces You need to add the Display Page selection to your content type's create/edit page to define the Display Page for each instance of that asset.}}{1691}{figure.584.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {585}Screen Navigation Framework}{1693}{chapter.585}\protected@file@percent }
\newlabel{screen-navigation-framework}{{585}{1693}{Screen Navigation Framework}{chapter.585}{}}
\@writefile{toc}{\contentsline {section}{\numberline {585.1}Using the Framework for Your Application}{1693}{section.585.1}\protected@file@percent }
\newlabel{using-the-framework-for-your-application}{{585.1}{1693}{Using the Framework for Your Application}{section.585.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {585.1}{\ignorespaces The User Management application has three Screen Navigation Categories: General, Contact, and Preference; and each of those have a number of Screen Navigation Entries}}{1694}{figure.585.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {585.2}{\ignorespaces Many application only use Screen Navigation Categories for their functionality, and don't have Screen Navigation Entries. For Blogs, Entries and Images are Categories with no Entries.}}{1694}{figure.585.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {585.2}Adding Custom Screens to Liferay Applications}{1695}{section.585.2}\protected@file@percent }
\newlabel{adding-custom-screens-to-liferay-applications}{{585.2}{1695}{Adding Custom Screens to Liferay Applications}{section.585.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {586}Using Screen Navigation for Your Application}{1697}{chapter.586}\protected@file@percent }
\newlabel{using-screen-navigation-for-your-application}{{586}{1697}{Using Screen Navigation for Your Application}{chapter.586}{}}
\@writefile{toc}{\contentsline {section}{\numberline {586.1}Adding Screens to Your Application's Back-end}{1697}{section.586.1}\protected@file@percent }
\newlabel{adding-screens-to-your-applications-back-end}{{586.1}{1697}{Adding Screens to Your Application's Back-end}{section.586.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {586.2}Adding Screens to Your Application's Front-end}{1700}{section.586.2}\protected@file@percent }
\newlabel{adding-screens-to-your-applications-front-end}{{586.2}{1700}{Adding Screens to Your Application's Front-end}{section.586.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {587}Extending Categories Administration}{1701}{chapter.587}\protected@file@percent }
\newlabel{extending-categories-administration}{{587}{1701}{Extending Categories Administration}{chapter.587}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {588}Developing a Fragment Renderer}{1703}{chapter.588}\protected@file@percent }
\newlabel{developing-a-fragment-renderer}{{588}{1703}{Developing a Fragment Renderer}{chapter.588}{}}
\@writefile{toc}{\contentsline {section}{\numberline {588.1}Implementing the FragmentRenderer Interface}{1703}{section.588.1}\protected@file@percent }
\newlabel{implementing-the-fragmentrenderer-interface}{{588.1}{1703}{Implementing the FragmentRenderer Interface}{section.588.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {588.2}Leveraging the FragmentRendererContext}{1704}{section.588.2}\protected@file@percent }
\newlabel{leveraging-the-fragmentrenderercontext}{{588.2}{1704}{Leveraging the FragmentRendererContext}{section.588.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {588.3}Rendering JSPs}{1704}{section.588.3}\protected@file@percent }
\newlabel{rendering-jsps}{{588.3}{1704}{Rendering JSPs}{section.588.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {588.4}Choosing When to Display a Component}{1705}{section.588.4}\protected@file@percent }
\newlabel{choosing-when-to-display-a-component}{{588.4}{1705}{Choosing When to Display a Component}{section.588.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {588.5}Translating the Collection Language Key}{1706}{section.588.5}\protected@file@percent }
\newlabel{translating-the-collection-language-key}{{588.5}{1706}{Translating the Collection Language Key}{section.588.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {589}Creating a Fragment Renderer}{1707}{chapter.589}\protected@file@percent }
\newlabel{creating-a-fragment-renderer}{{589}{1707}{Creating a Fragment Renderer}{chapter.589}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {589.1}{\ignorespaces The new Fragment Renderer appears in its defined component collection.}}{1708}{figure.589.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {589.2}{\ignorespaces When adding the new Fragment Renderer to a page, the context information is displayed.}}{1709}{figure.589.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {590}Web Services}{1711}{chapter.590}\protected@file@percent }
\newlabel{web-services}{{590}{1711}{Web Services}{chapter.590}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {591}Headless REST APIs}{1713}{chapter.591}\protected@file@percent }
\newlabel{headless-rest-apis}{{591}{1713}{Headless REST APIs}{chapter.591}{}}
\@writefile{toc}{\contentsline {section}{\numberline {591.1}OpenAPI}{1713}{section.591.1}\protected@file@percent }
\newlabel{openapi}{{591.1}{1713}{OpenAPI}{section.591.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {591.2}API Vocabulary}{1713}{section.591.2}\protected@file@percent }
\newlabel{api-vocabulary}{{591.2}{1713}{API Vocabulary}{section.591.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {592}Get Started: Find the API}{1715}{chapter.592}\protected@file@percent }
\newlabel{get-started-find-the-api}{{592}{1715}{Get Started: Find the API}{chapter.592}{}}
\@writefile{toc}{\contentsline {section}{\numberline {592.1}Related Topics}{1716}{section.592.1}\protected@file@percent }
\newlabel{related-topics-124}{{592.1}{1716}{Related Topics}{section.592.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {593}How To Invoke a Service}{1717}{chapter.593}\protected@file@percent }
\newlabel{how-to-invoke-a-service}{{593}{1717}{How To Invoke a Service}{chapter.593}{}}
\@writefile{toc}{\contentsline {section}{\numberline {593.1}Related Topics}{1719}{section.593.1}\protected@file@percent }
\newlabel{related-topics-125}{{593.1}{1719}{Related Topics}{section.593.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {594}Making Authenticated Requests}{1721}{chapter.594}\protected@file@percent }
\newlabel{making-authenticated-requests}{{594}{1721}{Making Authenticated Requests}{chapter.594}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.1}Basic Authentication}{1721}{section.594.1}\protected@file@percent }
\newlabel{basic-authentication}{{594.1}{1721}{Basic Authentication}{section.594.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.2}OAuth 2.0 Authorization}{1723}{section.594.2}\protected@file@percent }
\newlabel{oauth-2.0-authorization}{{594.2}{1723}{OAuth 2.0 Authorization}{section.594.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.3}Obtaining the OAuth 2.0 Token}{1723}{section.594.3}\protected@file@percent }
\newlabel{obtaining-the-oauth-2.0-token}{{594.3}{1723}{Obtaining the OAuth 2.0 Token}{section.594.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.4}Invoking the Service with an OAuth 2.0 Token}{1723}{section.594.4}\protected@file@percent }
\newlabel{invoking-the-service-with-an-oauth-2.0-token}{{594.4}{1723}{Invoking the Service with an OAuth 2.0 Token}{section.594.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.5}Using Cookie Authentication or Making Requests from the UI}{1723}{section.594.5}\protected@file@percent }
\newlabel{using-cookie-authentication-or-making-requests-from-the-ui}{{594.5}{1723}{Using Cookie Authentication or Making Requests from the UI}{section.594.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.6}Making Unauthenticated Requests}{1724}{section.594.6}\protected@file@percent }
\newlabel{making-unauthenticated-requests}{{594.6}{1724}{Making Unauthenticated Requests}{section.594.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.7}Cross-Origin Resource Sharing (CORS)}{1724}{section.594.7}\protected@file@percent }
\newlabel{cross-origin-resource-sharing-cors}{{594.7}{1724}{Cross-Origin Resource Sharing (CORS)}{section.594.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {594.8}Related Topics}{1724}{section.594.8}\protected@file@percent }
\newlabel{related-topics-126}{{594.8}{1724}{Related Topics}{section.594.8}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {594.1}{\ignorespaces Configure Cross-Origin Resource Sharing in Liferay}}{1725}{figure.594.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {595}Working with Collections of Data}{1727}{chapter.595}\protected@file@percent }
\newlabel{working-with-collections-of-data}{{595}{1727}{Working with Collections of Data}{chapter.595}{}}
\@writefile{toc}{\contentsline {section}{\numberline {595.1}Pagination}{1727}{section.595.1}\protected@file@percent }
\newlabel{pagination}{{595.1}{1727}{Pagination}{section.595.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {596}Getting Collections}{1729}{chapter.596}\protected@file@percent }
\newlabel{getting-collections}{{596}{1729}{Getting Collections}{chapter.596}{}}
\@writefile{toc}{\contentsline {section}{\numberline {596.1}Related Topics}{1730}{section.596.1}\protected@file@percent }
\newlabel{related-topics-127}{{596.1}{1730}{Related Topics}{section.596.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {597}Pagination}{1731}{chapter.597}\protected@file@percent }
\newlabel{pagination-1}{{597}{1731}{Pagination}{chapter.597}{}}
\@writefile{toc}{\contentsline {section}{\numberline {597.1}Related Topics}{1732}{section.597.1}\protected@file@percent }
\newlabel{related-topics-128}{{597.1}{1732}{Related Topics}{section.597.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {598}Navigating from a Collection to its Elements}{1733}{chapter.598}\protected@file@percent }
\newlabel{navigating-from-a-collection-to-its-elements}{{598}{1733}{Navigating from a Collection to its Elements}{chapter.598}{}}
\@writefile{toc}{\contentsline {section}{\numberline {598.1}Related Topics}{1734}{section.598.1}\protected@file@percent }
\newlabel{related-topics-129}{{598.1}{1734}{Related Topics}{section.598.1}{}}
\gdef \LT@xxii {\LT@entry
{1}{134.72264pt}\LT@entry
{1}{335.03236pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {599}API Formats and Content Negotiation}{1735}{chapter.599}\protected@file@percent }
\newlabel{api-formats-and-content-negotiation}{{599}{1735}{API Formats and Content Negotiation}{chapter.599}{}}
\@writefile{toc}{\contentsline {section}{\numberline {599.1}Language Negotiation}{1737}{section.599.1}\protected@file@percent }
\newlabel{language-negotiation}{{599.1}{1737}{Language Negotiation}{section.599.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {599.2}Creating Content with Different Languages}{1738}{section.599.2}\protected@file@percent }
\newlabel{creating-content-with-different-languages}{{599.2}{1738}{Creating Content with Different Languages}{section.599.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {599.3}Related Topics}{1738}{section.599.3}\protected@file@percent }
\newlabel{related-topics-130}{{599.3}{1738}{Related Topics}{section.599.3}{}}
\gdef \LT@xxiii {\LT@entry
{1}{134.72264pt}\LT@entry
{1}{335.03236pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {600}OpenAPI Profiles}{1739}{chapter.600}\protected@file@percent }
\newlabel{openapi-profiles}{{600}{1739}{OpenAPI Profiles}{chapter.600}{}}
\@writefile{toc}{\contentsline {section}{\numberline {600.1}Headless Delivery}{1739}{section.600.1}\protected@file@percent }
\newlabel{headless-delivery}{{600.1}{1739}{Headless Delivery}{section.600.1}{}}
\gdef \LT@xxiv {\LT@entry
{3}{71.0149pt}\LT@entry
{1}{81.60974pt}}
\gdef \LT@xxv {\LT@entry
{3}{96.02061pt}\LT@entry
{1}{81.60974pt}}
\@writefile{toc}{\contentsline {section}{\numberline {600.2}Headless Administration}{1740}{section.600.2}\protected@file@percent }
\newlabel{headless-administration}{{600.2}{1740}{Headless Administration}{section.600.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {600.3}Related Topics}{1740}{section.600.3}\protected@file@percent }
\newlabel{related-topics-131}{{600.3}{1740}{Related Topics}{section.600.3}{}}
\gdef \LT@xxvi {\LT@entry
{1}{49.99712pt}\LT@entry
{1}{68.9838pt}\LT@entry
{3}{151.0332pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {601}Filter, Sort, and Search}{1741}{chapter.601}\protected@file@percent }
\newlabel{filter-sort-and-search}{{601}{1741}{Filter, Sort, and Search}{chapter.601}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.1}Filter}{1741}{section.601.1}\protected@file@percent }
\newlabel{filter}{{601.1}{1741}{Filter}{section.601.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.2}Comparison Operators}{1741}{section.601.2}\protected@file@percent }
\newlabel{comparison-operators}{{601.2}{1741}{Comparison Operators}{section.601.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.3}Logical Operators}{1741}{section.601.3}\protected@file@percent }
\newlabel{logical-operators}{{601.3}{1741}{Logical Operators}{section.601.3}{}}
\gdef \LT@xxvii {\LT@entry
{1}{49.94234pt}\LT@entry
{1}{68.9838pt}\LT@entry
{3}{121.02634pt}}
\gdef \LT@xxviii {\LT@entry
{1}{104.33191pt}\LT@entry
{1}{84.12503pt}\LT@entry
{1}{281.29807pt}}
\@writefile{toc}{\contentsline {section}{\numberline {601.4}Grouping Operators}{1742}{section.601.4}\protected@file@percent }
\newlabel{grouping-operators}{{601.4}{1742}{Grouping Operators}{section.601.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.5}String Functions}{1742}{section.601.5}\protected@file@percent }
\newlabel{string-functions}{{601.5}{1742}{String Functions}{section.601.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.6}Lambda Operators}{1742}{section.601.6}\protected@file@percent }
\newlabel{lambda-operators}{{601.6}{1742}{Lambda Operators}{section.601.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.7}Operator combinations and OData syntax}{1742}{section.601.7}\protected@file@percent }
\newlabel{operator-combinations-and-odata-syntax}{{601.7}{1742}{Operator combinations and OData syntax}{section.601.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.8}Escaping in Queries}{1743}{section.601.8}\protected@file@percent }
\newlabel{escaping-in-queries}{{601.8}{1743}{Escaping in Queries}{section.601.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.9}Filtering in Structured Content Fields (ContentField)}{1743}{section.601.9}\protected@file@percent }
\newlabel{filtering-in-structured-content-fields-contentfield}{{601.9}{1743}{Filtering in Structured Content Fields (ContentField)}{section.601.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.10}Search}{1743}{section.601.10}\protected@file@percent }
\newlabel{search-2}{{601.10}{1743}{Search}{section.601.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.11}Sorting}{1744}{section.601.11}\protected@file@percent }
\newlabel{sorting}{{601.11}{1744}{Sorting}{section.601.11}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.12}Flatten}{1744}{section.601.12}\protected@file@percent }
\newlabel{flatten}{{601.12}{1744}{Flatten}{section.601.12}{}}
\@writefile{toc}{\contentsline {section}{\numberline {601.13}Related Topics}{1745}{section.601.13}\protected@file@percent }
\newlabel{related-topics-132}{{601.13}{1745}{Related Topics}{section.601.13}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {602}Restrict Properties}{1747}{chapter.602}\protected@file@percent }
\newlabel{restrict-properties}{{602}{1747}{Restrict Properties}{chapter.602}{}}
\@writefile{toc}{\contentsline {section}{\numberline {602.1}Related Topics}{1748}{section.602.1}\protected@file@percent }
\newlabel{related-topics-133}{{602.1}{1748}{Related Topics}{section.602.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {603}Multipart Requests}{1749}{chapter.603}\protected@file@percent }
\newlabel{multipart-requests}{{603}{1749}{Multipart Requests}{chapter.603}{}}
\@writefile{toc}{\contentsline {section}{\numberline {603.1}Related Topics}{1750}{section.603.1}\protected@file@percent }
\newlabel{related-topics-134}{{603.1}{1750}{Related Topics}{section.603.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {604}How to get siteId}{1751}{chapter.604}\protected@file@percent }
\newlabel{how-to-get-siteid}{{604}{1751}{How to get siteId}{chapter.604}{}}
\@writefile{toc}{\contentsline {section}{\numberline {604.1}Using siteId or siteKey}{1751}{section.604.1}\protected@file@percent }
\newlabel{using-siteid-or-sitekey}{{604.1}{1751}{Using siteId or siteKey}{section.604.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {604.2}Obtain siteId}{1751}{section.604.2}\protected@file@percent }
\newlabel{obtain-siteid}{{604.2}{1751}{Obtain siteId}{section.604.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {604.1}{\ignorespaces GraphQL BlogPostings definition}}{1752}{figure.604.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {605}Filterable properties}{1753}{chapter.605}\protected@file@percent }
\newlabel{filterable-properties}{{605}{1753}{Filterable properties}{chapter.605}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.1}Headless Delivery API}{1753}{section.605.1}\protected@file@percent }
\newlabel{headless-delivery-api}{{605.1}{1753}{Headless Delivery API}{section.605.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.2}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/BlogPosting}{BlogPosting}}{1753}{section.605.2}\protected@file@percent }
\newlabel{blogposting}{{605.2}{1753}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/BlogPosting}{BlogPosting}}{BlogPosting}}{section.605.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.3}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/BlogPostingImage}{BlogPostingImage}}{1753}{section.605.3}\protected@file@percent }
\newlabel{blogpostingimage}{{605.3}{1753}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/BlogPostingImage}{BlogPostingImage}}{BlogPostingImage}}{section.605.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.4}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/Comment}{Comment}}{1753}{section.605.4}\protected@file@percent }
\newlabel{comment}{{605.4}{1753}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/Comment}{Comment}}{Comment}}{section.605.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.5}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/ContentStructure}{ContentStructure}}{1754}{section.605.5}\protected@file@percent }
\newlabel{contentstructure}{{605.5}{1754}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/ContentStructure}{ContentStructure}}{ContentStructure}}{section.605.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.6}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/Document}{Document}}{1754}{section.605.6}\protected@file@percent }
\newlabel{document}{{605.6}{1754}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/Document}{Document}}{Document}}{section.605.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.7}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/DocumentFolder}{DocumentFolder}}{1754}{section.605.7}\protected@file@percent }
\newlabel{documentfolder}{{605.7}{1754}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/DocumentFolder}{DocumentFolder}}{DocumentFolder}}{section.605.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.8}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/KnowledgeBaseArticle}{KnowledgeBaseArticle}}{1754}{section.605.8}\protected@file@percent }
\newlabel{knowledgebasearticle}{{605.8}{1754}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/KnowledgeBaseArticle}{KnowledgeBaseArticle}}{KnowledgeBaseArticle}}{section.605.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.9}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/MessageBoardMessage}{MessageBoardMessage}}{1754}{section.605.9}\protected@file@percent }
\newlabel{messageboardmessage}{{605.9}{1754}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/MessageBoardMessage}{MessageBoardMessage}}{MessageBoardMessage}}{section.605.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.10}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/MessageBoardSection}{MessageBoardSection}}{1755}{section.605.10}\protected@file@percent }
\newlabel{messageboardsection}{{605.10}{1755}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/MessageBoardSection}{MessageBoardSection}}{MessageBoardSection}}{section.605.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.11}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/StructuredContent}{StructuredContent}}{1755}{section.605.11}\protected@file@percent }
\newlabel{structuredcontent}{{605.11}{1755}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/StructuredContent}{StructuredContent}}{StructuredContent}}{section.605.11}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.12}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/StructuredContentFolder}{StructuredContentFolder}}{1755}{section.605.12}\protected@file@percent }
\newlabel{structuredcontentfolder}{{605.12}{1755}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/StructuredContentFolder}{StructuredContentFolder}}{StructuredContentFolder}}{section.605.12}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.13}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/WikiNode}{WikiNode}}{1755}{section.605.13}\protected@file@percent }
\newlabel{wikinode}{{605.13}{1755}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/WikiNode}{WikiNode}}{WikiNode}}{section.605.13}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.14}\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/WikiPage}{WikiPage}}{1756}{section.605.14}\protected@file@percent }
\newlabel{wikipage}{{605.14}{1756}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/WikiPage}{WikiPage}}{WikiPage}}{section.605.14}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.15}Headless Admin User API}{1756}{section.605.15}\protected@file@percent }
\newlabel{headless-admin-user-api}{{605.15}{1756}{Headless Admin User API}{section.605.15}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.16}\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-user/v1.0\#/Organization}{Organization}}{1756}{section.605.16}\protected@file@percent }
\newlabel{organization}{{605.16}{1756}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-user/v1.0\#/Organization}{Organization}}{Organization}}{section.605.16}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.17}\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-user/v1.0\#/User}{User}}{1756}{section.605.17}\protected@file@percent }
\newlabel{user}{{605.17}{1756}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-user/v1.0\#/User}{User}}{User}}{section.605.17}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.18}Headless Admin Taxonomy API}{1756}{section.605.18}\protected@file@percent }
\newlabel{headless-admin-taxonomy-api}{{605.18}{1756}{Headless Admin Taxonomy API}{section.605.18}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.19}\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Category}{Category}}{1756}{section.605.19}\protected@file@percent }
\newlabel{category}{{605.19}{1756}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Category}{Category}}{Category}}{section.605.19}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.20}\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Keyword}{Keyword}}{1757}{section.605.20}\protected@file@percent }
\newlabel{keyword}{{605.20}{1757}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Keyword}{Keyword}}{Keyword}}{section.605.20}{}}
\@writefile{toc}{\contentsline {section}{\numberline {605.21}\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Vocabulary}{Vocabulary}}{1757}{section.605.21}\protected@file@percent }
\newlabel{vocabulary}{{605.21}{1757}{\texorpdfstring {\href {https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Vocabulary}{Vocabulary}}{Vocabulary}}{section.605.21}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {606}Using REST APIs}{1759}{chapter.606}\protected@file@percent }
\newlabel{using-rest-apis}{{606}{1759}{Using REST APIs}{chapter.606}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {607}JAX-RS}{1761}{chapter.607}\protected@file@percent }
\newlabel{jax-rs}{{607}{1761}{JAX-RS}{chapter.607}{}}
\@writefile{toc}{\contentsline {section}{\numberline {607.1}Authenticating to JAX-RS Web Services}{1762}{section.607.1}\protected@file@percent }
\newlabel{authenticating-to-jax-rs-web-services}{{607.1}{1762}{Authenticating to JAX-RS Web Services}{section.607.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {607.2}During Development: Basic Auth}{1762}{section.607.2}\protected@file@percent }
\newlabel{during-development-basic-auth}{{607.2}{1762}{During Development: Basic Auth}{section.607.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {607.3}Using OAuth 2.0 to Invoke a JAX-RS Web Service}{1762}{section.607.3}\protected@file@percent }
\newlabel{using-oauth-2.0-to-invoke-a-jax-rs-web-service}{{607.3}{1762}{Using OAuth 2.0 to Invoke a JAX-RS Web Service}{section.607.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {607.1}{\ignorespaces Enable the scope to grant access to the service.}}{1763}{figure.607.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{OAuth2 Scopes}{1764}{figure.607.1}\protected@file@percent }
\newlabel{oauth2-scopes}{{607.3}{1764}{OAuth2 Scopes}{figure.607.1}{}}
\@writefile{toc}{\contentsline {subsection}{Requiring OAuth2}{1764}{figure.607.1}\protected@file@percent }
\newlabel{requiring-oauth2}{{607.3}{1764}{Requiring OAuth2}{figure.607.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {607.4}JAX-RS and Service Access Policies}{1764}{section.607.4}\protected@file@percent }
\newlabel{jax-rs-and-service-access-policies}{{607.4}{1764}{JAX-RS and Service Access Policies}{section.607.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {607.5}Public JAX-RS Services}{1764}{section.607.5}\protected@file@percent }
\newlabel{public-jax-rs-services}{{607.5}{1764}{Public JAX-RS Services}{section.607.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {607.6}Using JAX-RS with CORS}{1765}{section.607.6}\protected@file@percent }
\newlabel{using-jax-rs-with-cors}{{607.6}{1765}{Using JAX-RS with CORS}{section.607.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {607.7}Related Topics}{1766}{section.607.7}\protected@file@percent }
\newlabel{related-topics-135}{{607.7}{1766}{Related Topics}{section.607.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {608}JAX-WS}{1767}{chapter.608}\protected@file@percent }
\newlabel{jax-ws}{{608}{1767}{JAX-WS}{chapter.608}{}}
\@writefile{toc}{\contentsline {section}{\numberline {608.1}Configuring Endpoints and Extenders with the Control Panel}{1767}{section.608.1}\protected@file@percent }
\newlabel{configuring-endpoints-and-extenders-with-the-control-panel}{{608.1}{1767}{Configuring Endpoints and Extenders with the Control Panel}{section.608.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {608.1}{\ignorespaces Fill out this form to create a CXF endpoint.}}{1768}{figure.608.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {608.2}Configuring Endpoints and Extenders Programmatically}{1769}{section.608.2}\protected@file@percent }
\newlabel{configuring-endpoints-and-extenders-programmatically}{{608.2}{1769}{Configuring Endpoints and Extenders Programmatically}{section.608.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {608.2}{\ignorespaces Fill out this form to create a SOAP extender.}}{1770}{figure.608.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {608.3}Publishing JAX-WS Web Services}{1771}{section.608.3}\protected@file@percent }
\newlabel{publishing-jax-ws-web-services}{{608.3}{1771}{Publishing JAX-WS Web Services}{section.608.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {609}GraphQL APIs}{1773}{chapter.609}\protected@file@percent }
\newlabel{graphql-apis}{{609}{1773}{GraphQL APIs}{chapter.609}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {610}Get Started: Discover the API}{1775}{chapter.610}\protected@file@percent }
\newlabel{get-started-discover-the-api}{{610}{1775}{Get Started: Discover the API}{chapter.610}{}}
\@writefile{toc}{\contentsline {section}{\numberline {610.1}Unique endpoint and versioning}{1775}{section.610.1}\protected@file@percent }
\newlabel{unique-endpoint-and-versioning}{{610.1}{1775}{Unique endpoint and versioning}{section.610.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {610.1}{\ignorespaces GraphQL APIs can be browsed in Altair.}}{1776}{figure.610.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {611}Get Started: Invoke a Service}{1777}{chapter.611}\protected@file@percent }
\newlabel{get-started-invoke-a-service}{{611}{1777}{Get Started: Invoke a Service}{chapter.611}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {611.1}{\ignorespaces GraphQL exposes a definition for BlogPostings.}}{1777}{figure.611.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {611.1}GraphQL Clients}{1779}{section.611.1}\protected@file@percent }
\newlabel{graphql-clients}{{611.1}{1779}{GraphQL Clients}{section.611.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {612}Making Authenticated Requests}{1781}{chapter.612}\protected@file@percent }
\newlabel{making-authenticated-requests-1}{{612}{1781}{Making Authenticated Requests}{chapter.612}{}}
\@writefile{toc}{\contentsline {section}{\numberline {612.1}Basic Authentication}{1781}{section.612.1}\protected@file@percent }
\newlabel{basic-authentication-1}{{612.1}{1781}{Basic Authentication}{section.612.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {612.2}OAuth 2.0 Authorization}{1782}{section.612.2}\protected@file@percent }
\newlabel{oauth-2.0-authorization-1}{{612.2}{1782}{OAuth 2.0 Authorization}{section.612.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {612.3}Obtaining the OAuth 2.0 Token}{1782}{section.612.3}\protected@file@percent }
\newlabel{obtaining-the-oauth-2.0-token-1}{{612.3}{1782}{Obtaining the OAuth 2.0 Token}{section.612.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {612.4}Invoking the Service with an OAuth 2.0 Token}{1782}{section.612.4}\protected@file@percent }
\newlabel{invoking-the-service-with-an-oauth-2.0-token-1}{{612.4}{1782}{Invoking the Service with an OAuth 2.0 Token}{section.612.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {612.5}Using Cookie Authentication or doing a request from the portal}{1783}{section.612.5}\protected@file@percent }
\newlabel{using-cookie-authentication-or-doing-a-request-from-the-portal}{{612.5}{1783}{Using Cookie Authentication or doing a request from the portal}{section.612.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {612.6}Making Unauthenticated Requests}{1783}{section.612.6}\protected@file@percent }
\newlabel{making-unauthenticated-requests-1}{{612.6}{1783}{Making Unauthenticated Requests}{section.612.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {612.7}Related Topics}{1783}{section.612.7}\protected@file@percent }
\newlabel{related-topics-136}{{612.7}{1783}{Related Topics}{section.612.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {613}Working with Collections of Data}{1785}{chapter.613}\protected@file@percent }
\newlabel{working-with-collections-of-data-1}{{613}{1785}{Working with Collections of Data}{chapter.613}{}}
\@writefile{toc}{\contentsline {section}{\numberline {613.1}Pagination}{1785}{section.613.1}\protected@file@percent }
\newlabel{pagination-2}{{613.1}{1785}{Pagination}{section.613.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {614}Mutations}{1787}{chapter.614}\protected@file@percent }
\newlabel{mutations}{{614}{1787}{Mutations}{chapter.614}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {614.1}{\ignorespaces The GraphQL Mutations list for Blog postings shows the possible operations.}}{1787}{figure.614.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {615}Fragments and Node Patterns}{1789}{chapter.615}\protected@file@percent }
\newlabel{fragments-and-node-patterns}{{615}{1789}{Fragments and Node Patterns}{chapter.615}{}}
\@writefile{toc}{\contentsline {section}{\numberline {615.1}Node pattern}{1789}{section.615.1}\protected@file@percent }
\newlabel{node-pattern}{{615.1}{1789}{Node pattern}{section.615.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {616}Language Negotiation}{1791}{chapter.616}\protected@file@percent }
\newlabel{language-negotiation-1}{{616}{1791}{Language Negotiation}{chapter.616}{}}
\@writefile{toc}{\contentsline {section}{\numberline {616.1}Creating Content with Different Languages}{1792}{section.616.1}\protected@file@percent }
\newlabel{creating-content-with-different-languages-1}{{616.1}{1792}{Creating Content with Different Languages}{section.616.1}{}}
\gdef \LT@xxix {\LT@entry
{1}{49.99712pt}\LT@entry
{1}{68.9838pt}\LT@entry
{3}{151.0332pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {617}Filter, Sort, and Search}{1793}{chapter.617}\protected@file@percent }
\newlabel{filter-sort-and-search-1}{{617}{1793}{Filter, Sort, and Search}{chapter.617}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.1}Filter}{1793}{section.617.1}\protected@file@percent }
\newlabel{filter-1}{{617.1}{1793}{Filter}{section.617.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.2}Comparison Operators}{1793}{section.617.2}\protected@file@percent }
\newlabel{comparison-operators-1}{{617.2}{1793}{Comparison Operators}{section.617.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.3}Logical Operators}{1793}{section.617.3}\protected@file@percent }
\newlabel{logical-operators-1}{{617.3}{1793}{Logical Operators}{section.617.3}{}}
\gdef \LT@xxx {\LT@entry
{1}{49.94234pt}\LT@entry
{1}{68.9838pt}\LT@entry
{3}{121.02634pt}}
\gdef \LT@xxxi {\LT@entry
{1}{104.33191pt}\LT@entry
{1}{84.12503pt}\LT@entry
{1}{281.29807pt}}
\@writefile{toc}{\contentsline {section}{\numberline {617.4}Grouping Operators}{1794}{section.617.4}\protected@file@percent }
\newlabel{grouping-operators-1}{{617.4}{1794}{Grouping Operators}{section.617.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.5}String Functions}{1794}{section.617.5}\protected@file@percent }
\newlabel{string-functions-1}{{617.5}{1794}{String Functions}{section.617.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.6}Lambda Operators}{1794}{section.617.6}\protected@file@percent }
\newlabel{lambda-operators-1}{{617.6}{1794}{Lambda Operators}{section.617.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.7}Escaping in Queries}{1794}{section.617.7}\protected@file@percent }
\newlabel{escaping-in-queries-1}{{617.7}{1794}{Escaping in Queries}{section.617.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.8}Filtering in Structured Content Fields (ContentField)}{1795}{section.617.8}\protected@file@percent }
\newlabel{filtering-in-structured-content-fields-contentfield-1}{{617.8}{1795}{Filtering in Structured Content Fields (ContentField)}{section.617.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.9}Search}{1795}{section.617.9}\protected@file@percent }
\newlabel{search-3}{{617.9}{1795}{Search}{section.617.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.10}Sorting}{1796}{section.617.10}\protected@file@percent }
\newlabel{sorting-1}{{617.10}{1796}{Sorting}{section.617.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {617.11}Flatten}{1797}{section.617.11}\protected@file@percent }
\newlabel{flatten-1}{{617.11}{1797}{Flatten}{section.617.11}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {618}Multipart Requests}{1799}{chapter.618}\protected@file@percent }
\newlabel{multipart-requests-1}{{618}{1799}{Multipart Requests}{chapter.618}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {618.1}{\ignorespaces Create Document accepts a \texttt {multipartBody}.}}{1799}{figure.618.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {618.2}{\ignorespaces Creating a Document in Altair is easy with the selector.}}{1800}{figure.618.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {619}Using GraphQL APIs}{1803}{chapter.619}\protected@file@percent }
\newlabel{using-graphql-apis}{{619}{1803}{Using GraphQL APIs}{chapter.619}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {620}REST Builder}{1805}{chapter.620}\protected@file@percent }
\newlabel{rest-builder}{{620}{1805}{REST Builder}{chapter.620}{}}
\@writefile{toc}{\contentsline {section}{\numberline {620.1}Why we should use REST Builder}{1805}{section.620.1}\protected@file@percent }
\newlabel{why-we-should-use-rest-builder}{{620.1}{1805}{Why we should use REST Builder}{section.620.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {621}How to install REST Builder}{1807}{chapter.621}\protected@file@percent }
\newlabel{how-to-install-rest-builder}{{621}{1807}{How to install REST Builder}{chapter.621}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {622}REST Builder \& OpenAPI}{1809}{chapter.622}\protected@file@percent }
\newlabel{rest-builder-openapi}{{622}{1809}{REST Builder \& OpenAPI}{chapter.622}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.1}OpenAPI profile}{1809}{section.622.1}\protected@file@percent }
\newlabel{openapi-profile}{{622.1}{1809}{OpenAPI profile}{section.622.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.2}Generation}{1811}{section.622.2}\protected@file@percent }
\newlabel{generation}{{622.2}{1811}{Generation}{section.622.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.3}Examples}{1811}{section.622.3}\protected@file@percent }
\newlabel{examples}{{622.3}{1811}{Examples}{section.622.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.4}GET Collection}{1811}{section.622.4}\protected@file@percent }
\newlabel{get-collection}{{622.4}{1811}{GET Collection}{section.622.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.5}DELETE}{1812}{section.622.5}\protected@file@percent }
\newlabel{delete}{{622.5}{1812}{DELETE}{section.622.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.6}POST}{1812}{section.622.6}\protected@file@percent }
\newlabel{post}{{622.6}{1812}{POST}{section.622.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.7}PUT}{1812}{section.622.7}\protected@file@percent }
\newlabel{put}{{622.7}{1812}{PUT}{section.622.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {622.8}Summary}{1813}{section.622.8}\protected@file@percent }
\newlabel{summary}{{622.8}{1813}{Summary}{section.622.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {623}Developing an API with REST Builder}{1815}{chapter.623}\protected@file@percent }
\newlabel{developing-an-api-with-rest-builder}{{623}{1815}{Developing an API with REST Builder}{chapter.623}{}}
\@writefile{toc}{\contentsline {section}{\numberline {623.1}Development Cycle}{1816}{section.623.1}\protected@file@percent }
\newlabel{development-cycle}{{623.1}{1816}{Development Cycle}{section.623.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {623.2}Wrapping Up}{1817}{section.623.2}\protected@file@percent }
\newlabel{wrapping-up}{{623.2}{1817}{Wrapping Up}{section.623.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {624}Managing Collections in REST Builder}{1819}{chapter.624}\protected@file@percent }
\newlabel{managing-collections-in-rest-builder}{{624}{1819}{Managing Collections in REST Builder}{chapter.624}{}}
\@writefile{toc}{\contentsline {section}{\numberline {624.1}Pagination}{1819}{section.624.1}\protected@file@percent }
\newlabel{pagination-3}{{624.1}{1819}{Pagination}{section.624.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {624.2}Filtering, sorting and searching}{1819}{section.624.2}\protected@file@percent }
\newlabel{filtering-sorting-and-searching}{{624.2}{1819}{Filtering, sorting and searching}{section.624.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {624.3}Add an EntityModel}{1820}{section.624.3}\protected@file@percent }
\newlabel{add-an-entitymodel}{{624.3}{1820}{Add an EntityModel}{section.624.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {624.4}Inject Your EntityModel}{1820}{section.624.4}\protected@file@percent }
\newlabel{inject-your-entitymodel}{{624.4}{1820}{Inject Your EntityModel}{section.624.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {624.5}Call search utilities}{1821}{section.624.5}\protected@file@percent }
\newlabel{call-search-utilities}{{624.5}{1821}{Call search utilities}{section.624.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {624.6}Using Your filter, search, and sort}{1821}{section.624.6}\protected@file@percent }
\newlabel{using-your-filter-search-and-sort}{{624.6}{1821}{Using Your filter, search, and sort}{section.624.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {625}REST Builder Scaffolding}{1823}{chapter.625}\protected@file@percent }
\newlabel{rest-builder-scaffolding}{{625}{1823}{REST Builder Scaffolding}{chapter.625}{}}
\@writefile{toc}{\contentsline {section}{\numberline {625.1}Context fields}{1823}{section.625.1}\protected@file@percent }
\newlabel{context-fields}{{625.1}{1823}{Context fields}{section.625.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {625.2}Automatic transactions}{1823}{section.625.2}\protected@file@percent }
\newlabel{automatic-transactions}{{625.2}{1823}{Automatic transactions}{section.625.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {625.3}Test generation}{1824}{section.625.3}\protected@file@percent }
\newlabel{test-generation}{{625.3}{1824}{Test generation}{section.625.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {625.4}Client generation}{1824}{section.625.4}\protected@file@percent }
\newlabel{client-generation}{{625.4}{1824}{Client generation}{section.625.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {625.5}Common utilities}{1824}{section.625.5}\protected@file@percent }
\newlabel{common-utilities}{{625.5}{1824}{Common utilities}{section.625.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {626}Support for oneOf, anyOf and allOf}{1825}{chapter.626}\protected@file@percent }
\newlabel{support-for-oneof-anyof-and-allof}{{626}{1825}{Support for oneOf, anyOf and allOf}{chapter.626}{}}
\@writefile{toc}{\contentsline {section}{\numberline {626.1}allOf}{1825}{section.626.1}\protected@file@percent }
\newlabel{allof}{{626.1}{1825}{allOf}{section.626.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {626.2}oneOf}{1826}{section.626.2}\protected@file@percent }
\newlabel{oneof}{{626.2}{1826}{oneOf}{section.626.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {626.3}anyOf}{1826}{section.626.3}\protected@file@percent }
\newlabel{anyof}{{626.3}{1826}{anyOf}{section.626.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {627}REST Builder Liferay Conventions}{1829}{chapter.627}\protected@file@percent }
\newlabel{rest-builder-liferay-conventions}{{627}{1829}{REST Builder Liferay Conventions}{chapter.627}{}}
\@writefile{toc}{\contentsline {section}{\numberline {627.1}YAML \& OpenAPI restrictions}{1829}{section.627.1}\protected@file@percent }
\newlabel{yaml-openapi-restrictions}{{627.1}{1829}{YAML \& OpenAPI restrictions}{section.627.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {627.2}Conventions}{1829}{section.627.2}\protected@file@percent }
\newlabel{conventions}{{627.2}{1829}{Conventions}{section.627.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {628}The Workflow Framework}{1831}{chapter.628}\protected@file@percent }
\newlabel{the-workflow-framework}{{628}{1831}{The Workflow Framework}{chapter.628}{}}
\@writefile{toc}{\contentsline {section}{\numberline {628.1}Supporting Workflow in the Database}{1831}{section.628.1}\protected@file@percent }
\newlabel{supporting-workflow-in-the-database}{{628.1}{1831}{Supporting Workflow in the Database}{section.628.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {628.2}Setting the Status Fields}{1831}{section.628.2}\protected@file@percent }
\newlabel{setting-the-status-fields}{{628.2}{1831}{Setting the Status Fields}{section.628.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {628.1}{\ignorespaces Enable workflow on your custom Asset, and it can be sent through a workflow process just like a native Asset.}}{1832}{figure.628.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {628.3}Sending the Entity to the Workflow Framework}{1833}{section.628.3}\protected@file@percent }
\newlabel{sending-the-entity-to-the-workflow-framework}{{628.3}{1833}{Sending the Entity to the Workflow Framework}{section.628.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {628.4}Allowing the Workflow Framework to Handle the Entity}{1833}{section.628.4}\protected@file@percent }
\newlabel{allowing-the-workflow-framework-to-handle-the-entity}{{628.4}{1833}{Allowing the Workflow Framework to Handle the Entity}{section.628.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {628.5}Supporting Workflow in the Service Layer}{1833}{section.628.5}\protected@file@percent }
\newlabel{supporting-workflow-in-the-service-layer}{{628.5}{1833}{Supporting Workflow in the Service Layer}{section.628.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {628.6}Database Cleanup: Delete the Workflow Instance Links}{1834}{section.628.6}\protected@file@percent }
\newlabel{database-cleanup-delete-the-workflow-instance-links}{{628.6}{1834}{Database Cleanup: Delete the Workflow Instance Links}{section.628.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {628.7}Updating the User Interface}{1834}{section.628.7}\protected@file@percent }
\newlabel{updating-the-user-interface}{{628.7}{1834}{Updating the User Interface}{section.628.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {629}Liferay's Workflow Framework}{1835}{chapter.629}\protected@file@percent }
\newlabel{liferays-workflow-framework}{{629}{1835}{Liferay's Workflow Framework}{chapter.629}{}}
\@writefile{toc}{\contentsline {section}{\numberline {629.1}Creating a Workflow Handler}{1835}{section.629.1}\protected@file@percent }
\newlabel{creating-a-workflow-handler}{{629.1}{1835}{Creating a Workflow Handler}{section.629.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {629.2}Updating the Service Layer}{1836}{section.629.2}\protected@file@percent }
\newlabel{updating-the-service-layer}{{629.2}{1836}{Updating the Service Layer}{section.629.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {629.3}Workflow Status and the View Layer}{1838}{section.629.3}\protected@file@percent }
\newlabel{workflow-status-and-the-view-layer}{{629.3}{1838}{Workflow Status and the View Layer}{section.629.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {630}WYSIWYG Editors}{1839}{chapter.630}\protected@file@percent }
\newlabel{wysiwyg-editors}{{630}{1839}{WYSIWYG Editors}{chapter.630}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {631}Adding a WYSIWYG Editor to a Portlet}{1841}{chapter.631}\protected@file@percent }
\newlabel{adding-a-wysiwyg-editor-to-a-portlet}{{631}{1841}{Adding a WYSIWYG Editor to a Portlet}{chapter.631}{}}
\@writefile{toc}{\contentsline {section}{\numberline {631.1}Related Topics}{1842}{section.631.1}\protected@file@percent }
\newlabel{related-topics-137}{{631.1}{1842}{Related Topics}{section.631.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {632}Modifying an Editor's Configuration}{1843}{chapter.632}\protected@file@percent }
\newlabel{modifying-an-editors-configuration}{{632}{1843}{Modifying an Editor's Configuration}{chapter.632}{}}
\@writefile{toc}{\contentsline {section}{\numberline {632.1}Related Topics}{1846}{section.632.1}\protected@file@percent }
\newlabel{related-topics-138}{{632.1}{1846}{Related Topics}{section.632.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {633}AlloyEditor}{1847}{chapter.633}\protected@file@percent }
\newlabel{alloyeditor}{{633}{1847}{AlloyEditor}{chapter.633}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {633.1}{\ignorespaces AlloyEditor is the default WYSIWYG editor built on top of CKEditor.}}{1847}{figure.633.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {634}Adding Buttons to AlloyEditor's Toolbars}{1849}{chapter.634}\protected@file@percent }
\newlabel{adding-buttons-to-alloyeditors-toolbars}{{634}{1849}{Adding Buttons to AlloyEditor's Toolbars}{chapter.634}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {635}Creating the OSGi Module and Configuring the EditorConfigContributor Class}{1851}{chapter.635}\protected@file@percent }
\newlabel{creating-the-osgi-module-and-configuring-the-editorconfigcontributor-class}{{635}{1851}{Creating the OSGi Module and Configuring the EditorConfigContributor Class}{chapter.635}{}}
\@writefile{toc}{\contentsline {section}{\numberline {635.1}Related Topics}{1852}{section.635.1}\protected@file@percent }
\newlabel{related-topics-139}{{635.1}{1852}{Related Topics}{section.635.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {636}Adding a Button to the Add Toolbar}{1853}{chapter.636}\protected@file@percent }
\newlabel{adding-a-button-to-the-add-toolbar}{{636}{1853}{Adding a Button to the Add Toolbar}{chapter.636}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {636.1}{\ignorespaces The Add toolbar lets you add content to the editor.}}{1853}{figure.636.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {636.2}{\ignorespaces The Updated Add toolbar lets you add pictures from a camera directly to the editor.}}{1854}{figure.636.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {636.1}Related Topics}{1855}{section.636.1}\protected@file@percent }
\newlabel{related-topics-140}{{636.1}{1855}{Related Topics}{section.636.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {637}Adding a Button to a Styles Toolbar}{1857}{chapter.637}\protected@file@percent }
\newlabel{adding-a-button-to-a-styles-toolbar}{{637}{1857}{Adding a Button to a Styles Toolbar}{chapter.637}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {637.1}{\ignorespaces The embed URL Styles toolbar lets you format embedded content in the editor.}}{1857}{figure.637.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {637.2}{\ignorespaces The image Styles toolbar lets you format images in the editor.}}{1858}{figure.637.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {637.3}{\ignorespaces The link Styles toolbar lets you format hyperlinks in the editor.}}{1859}{figure.637.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {637.4}{\ignorespaces The table Styles toolbar lets you format tables in the editor.}}{1859}{figure.637.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {637.5}{\ignorespaces The text Styles toolbar lets you format highlighted text in the editor.}}{1860}{figure.637.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {637.6}{\ignorespaces The Updated text styles toolbar lets you copy, cut, and paste text in the editor.}}{1861}{figure.637.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {637.1}Related Topics}{1862}{section.637.1}\protected@file@percent }
\newlabel{related-topics-141}{{637.1}{1862}{Related Topics}{section.637.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {638}Embedding Content in the AlloyEditor}{1863}{chapter.638}\protected@file@percent }
\newlabel{embedding-content-in-the-alloyeditor}{{638}{1863}{Embedding Content in the AlloyEditor}{chapter.638}{}}
\@writefile{toc}{\contentsline {section}{\numberline {638.1}Related Topics}{1865}{section.638.1}\protected@file@percent }
\newlabel{related-topics-142}{{638.1}{1865}{Related Topics}{section.638.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {639}Adding New Behavior to an Editor}{1867}{chapter.639}\protected@file@percent }
\newlabel{adding-new-behavior-to-an-editor}{{639}{1867}{Adding New Behavior to an Editor}{chapter.639}{}}
\@writefile{toc}{\contentsline {section}{\numberline {639.1}Related Topics}{1869}{section.639.1}\protected@file@percent }
\newlabel{related-topics-143}{{639.1}{1869}{Related Topics}{section.639.1}{}}
\@setckpt{developer/frameworks}{
\setcounter{page}{1870}
\setcounter{equation}{0}
\setcounter{enumi}{8}
\setcounter{enumii}{4}
\setcounter{enumiii}{3}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{@memmarkcntra}{0}
\setcounter{storedpagenumber}{1}
\setcounter{book}{0}
\setcounter{part}{4}
\setcounter{chapter}{639}
\setcounter{section}{1}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{@ppsavesec}{0}
\setcounter{@ppsaveapp}{0}
\setcounter{vslineno}{0}
\setcounter{poemline}{0}
\setcounter{modulo@vs}{0}
\setcounter{memfvsline}{0}
\setcounter{verse}{0}
\setcounter{chrsinstr}{0}
\setcounter{poem}{0}
\setcounter{newflo@tctr}{4}
\setcounter{@contsubnum}{0}
\setcounter{section@level}{0}
\setcounter{maxsecnumdepth}{1}
\setcounter{sidefootnote}{0}
\setcounter{pagenote}{0}
\setcounter{pagenoteshadow}{0}
\setcounter{memfbvline}{0}
\setcounter{bvlinectr}{0}
\setcounter{cp@cntr}{0}
\setcounter{ism@mctr}{0}
\setcounter{xsm@mctr}{0}
\setcounter{csm@mctr}{0}
\setcounter{ksm@mctr}{0}
\setcounter{xksm@mctr}{0}
\setcounter{cksm@mctr}{0}
\setcounter{msm@mctr}{0}
\setcounter{xmsm@mctr}{0}
\setcounter{cmsm@mctr}{0}
\setcounter{bsm@mctr}{0}
\setcounter{workm@mctr}{0}
\setcounter{sheetsequence}{1942}
\setcounter{lastsheet}{2851}
\setcounter{lastpage}{2779}
\setcounter{figure}{0}
\setcounter{lofdepth}{1}
\setcounter{table}{0}
\setcounter{lotdepth}{1}
\setcounter{Item}{2116}
\setcounter{Hfootnote}{5}
\setcounter{bookmark@seq@number}{0}
\setcounter{memhycontfloat}{0}
\setcounter{Hpagenote}{0}
\setcounter{r@tfl@t}{0}
\setcounter{float@type}{4}
\setcounter{LT@tables}{31}
\setcounter{LT@chunks}{3}
\setcounter{parentequation}{0}
\setcounter{FancyVerbLine}{0}
}
================================================
FILE: book/developer/frameworks.tex
================================================
\chapter{Application Security}\label{application-security}
Liferay's development framework provides an application security
platform with years of experience behind it. You don't need to roll your
own security for your applications. Instead, you can specify security
for your applications using Liferay's framework.
Beyond security for applications, there are many ways to extend the
default security model by customizing the authentication process. This
group of tutorials teaches you about them:
\begin{itemize}
\tightlist
\item
Resources, Roles, and Permissions
\item
Custom SSO Providers
\item
Authentication Pipelines
\item
Service Access Policies
\item
Authentication Verifiers
\end{itemize}
Read on to learn about implementing Liferay's security framework!
\chapter{Defining Application
Permissions}\label{defining-application-permissions}
When you're writing an application, there are almost always parts of the
application or its data that should be protected by permissions. Some
users should access all the functions or data, but most users shouldn't.
On many platforms, developers have to create the security model
themselves. On Liferay DXP, an application security model has been
provided for you; you only need to make use of it.
Fortunately, no matter what your application does, access to it and to
its content can be controlled with permissions. Read on to learn about
Liferay's permissions system and how add permissions to your
application.
The permissions system has three parts: \emph{Resources},
\emph{Actions}, and \emph{Permissions}.
\textbf{Action}: An operation that can be performed by a user. For
example, users can perform these actions on the Bookmarks application:
\texttt{ADD\_TO\_PAGE}, \texttt{CONFIGURATION}, and \texttt{VIEW}. Users
can perform these actions on Bookmarks entry entities:
\texttt{ADD\_ENTRY}, \texttt{DELETE}, \texttt{PERMISSIONS},
\texttt{UPDATE}, and \texttt{VIEW}.
\textbf{Resource}: A generic representation of any application or entity
on which an action can be performed. Resources are used for permission
checking. For example, resources could include the RSS application with
instance ID \texttt{hF5f}, a globally scoped Wiki page, a Bookmarks
entry of the site X, and a Message Boards post with the ID
\texttt{5052}.
\textbf{Permission}: A flag that determines whether an action can be
performed on a resource. In the database, resources and actions are
saved in pairs. Each entry in the \texttt{ResourceAction} table contains
both the name of a portlet or entity and the name of an action. For
example, the \texttt{VIEW} action with respect to \emph{viewing the
Bookmarks application} is associated with the
\texttt{com\_liferay\_bookmarks\_web\_portlet\_BookmarksPortlet} portlet
ID. The \texttt{VIEW} actions with respect to \emph{viewing a Bookmarks
Folder} or \emph{viewing a Bookmarks entry} are associated with the
\texttt{com.liferay.bookmarks.model.BookmarksFolder} and
\texttt{com.liferay.bookmarks.model.BookmarksEntry} entities,
respectively.
To do permissions, therefore, you define \emph{Users} (Roles) who have
\emph{Permission} to perform \emph{Actions} on \emph{Resources}. User
definition is done by administrators once your application is deployed;
developers define resources, actions, and default permissions.
You can implement permissions in your applications in four steps that
spell the acronym \emph{DRAC}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Define all resources and their permissions.
\item
Register all defined resources in the permissions system.
\item
Associate the necessary permissions with resources.
\item
Check permission before returning resources.
\end{enumerate}
The next four tutorials show these steps in detail.
\chapter{Defining Resources and
Permissions}\label{defining-resources-and-permissions}
Your first step in implementing permissions is to define the resources
and the permissions that protect them. There are two different kinds of
resources: \emph{portlet resources} and \emph{model resources}.
Portlet resources represent portlets. The names of portlet resources are
the portlet IDs from the portlets' \texttt{@Component} properties or if
you're using a WAR file, \texttt{portlet.xml} files. Model resources
refer to model objects, usually persisted as entities to a database. The
names of model resources are their fully qualified class names. In the
XML displayed below, permission implementations are first defined for
the \emph{portlet} resource and then for the \emph{model} resources.
Model resources represent models, such as blog entries. Resources are
named using the fully qualified class names of the entities they
represent.
\noindent\hrulefill
\textbf{Note:} For each resource, there are four scopes to which the
permissions can be applied: company, group, group-template, or
individual. Because these are called \emph{portlet resources} here and
in the code, this can be confusing. The other scopes are mostly used
internally for various Liferay constructs (such as Sites or Categories).
\noindent\hrulefill
You define resources and their permissions using an XML file. By
convention, this file is called \texttt{default.xml} and exists in a
module's \texttt{src/main/resources/resource-actions} folder.
Because of the two different types of resources, you'll have two of
these files: one in your portlet module to define the portlet resources
and one in your service module to define the model resources.
\section{Defining Portlet Resource
Permissions}\label{defining-portlet-resource-permissions}
Define the portlet resources first; here's an example using Liferay's
Blogs application.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Start with the DTD declaration:
\begin{verbatim}
\end{verbatim}
\item
The root tag contains all the resources to be declared:
\begin{verbatim}
\end{verbatim}
\item
Inside these tags, define your resources. The Blogs application
defines two portlet resources:
\begin{verbatim}
com_liferay_blogs_web_portlet_BlogsAdminPortlet
ACCESS_IN_CONTROL_PANEL
CONFIGURATION
VIEW
VIEW
VIEW
ACCESS_IN_CONTROL_PANEL
CONFIGURATION
com_liferay_blogs_web_portlet_BlogsPortlet
ADD_PORTLET_DISPLAY_TEMPLATE
ADD_TO_PAGE
CONFIGURATION
VIEW
VIEW
VIEW
ADD_PORTLET_DISPLAY_TEMPLATE
CONFIGURATION
\end{verbatim}
\end{enumerate}
The Blogs application comprises two portlets: the Blogs portlet itself
and the Blogs Admin portlet that appears in the Site menu for
administrators. Define your portlets by their names, and then list the
permissions for the portlet. The Blogs portlet, for example, supports
four permissions: \texttt{ADD\_PORTLET\_DISPLAY\_TEMPLATE},
\texttt{ADD\_TO\_PAGE}, \texttt{CONFIGURATION}, and \texttt{VIEW}. The
Blogs Admin portlet has an additional permission:
\texttt{ACCESS\_IN\_CONTROL\_PANEL}, which defines who can see the entry
in the Site menu.
Once you've defined permissions at the portlet level, you can set
default permissions for different types of users. The DTD allows for
site member and guest defaults. Since guests are users that aren't
logged in, there's also a \texttt{guest-unsupported} tag for defining
permissions guests can \emph{never} have (in other words, the user must
be logged in and identifiable).
That's all there is to it! Your next task is to define permissions for
your model resources.
\section{Defining Model Resource
Permissions}\label{defining-model-resource-permissions}
Defining permissions for models is a similar process. Create a
\texttt{default.xml} file in your service module's
\texttt{src/main/resources/resource-actions} folder. In this file, you
must define top-level function permissions and individual entity
permissions using the same
\texttt{\textless{}model-resource\textgreater{}} tag.
This can be confusing, so some explanation is in order. Model
permissions for what Liferay calls the \emph{root model} are defined
separately from permissions on stored entities, which Liferay calls the
\emph{model}. This makes sense when you think about the functions users
can perform:
\begin{itemize}
\tightlist
\item
Creating something new
\item
Editing something that exists
\end{itemize}
Creating something new (like adding a new Blog entry) is different from
accessing something that exists. A Blog owner should be able to create
or edit a Blog entry, but a User or guest should have read permission
for existing entries and no permission to create them.
Permission to create something new that doesn't yet exist is a
\emph{root model} permission, whether that functionality is exposed in a
portlet or not. Permission on an existing resource is a \emph{model}
permission.
Now you're ready to define both your root model and model permissions.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
First, create the skeleton for your file:
\begin{verbatim}
\end{verbatim}
\item
Inside the \texttt{\textless{}resource-action-mapping\textgreater{}}
tags, use a \texttt{\textless{}model-resource\textgreater{}} tag to
define permissions for the root model:
\begin{verbatim}
com.liferay.blogs
com_liferay_blogs_web_portlet_BlogsAdminPortlet
com_liferay_blogs_web_portlet_BlogsPortlet
true
1
ADD_ENTRY
PERMISSIONS
SUBSCRIBE
SUBSCRIBE
ADD_ENTRY
PERMISSIONS
SUBSCRIBE
\end{verbatim}
The model name (\texttt{com.liferay.blogs}) is just a package name.
The
\texttt{\textless{}root\textgreater{}true\textless{}/root\textgreater{}}
tag defines this as a root model. The
\texttt{\textless{}weight\textgreater{}} tag defines the order of
these permissions in the GUI. The permissions defined are ADD\_ENTRY
(add a Blog entry), PERMISSIONS (set permissions on Blog entries), and
SUBSCRIBE (receive notifications when Blog entries are created). These
are all root model permissions, because no primary key in the database
can be assigned to any of these functions.
\item
Finally, define your model permissions:
\begin{verbatim}
com.liferay.blogs.model.BlogsEntry
com_liferay_blogs_web_portlet_BlogsAdminPortlet
com_liferay_blogs_web_portlet_BlogsPortlet
2
ADD_DISCUSSION
DELETE
DELETE_DISCUSSION
PERMISSIONS
UPDATE
UPDATE_DISCUSSION
VIEW
ADD_DISCUSSION
VIEW
ADD_DISCUSSION
VIEW
DELETE
DELETE_DISCUSSION
PERMISSIONS
UPDATE
UPDATE_DISCUSSION
\end{verbatim}
\end{enumerate}
Note the lack of a \texttt{\textless{}root\textgreater{}} tag, the fully
qualified class name for the model, and the permissions that operate on
an entity with a primary key.
\section{Enabling Your Permissions
Configuration}\label{enabling-your-permissions-configuration}
Your last step is to enable your permission definitions. Each module
that contains a \texttt{default.xml} permissions definition file must
also have a \texttt{portlet.properties} file with a property that
defines where to find the permissions definition file. For your service
and your web modules, create a \texttt{portlet.properties} file in
\texttt{src/main/resources} and make sure it has this property:
\begin{verbatim}
resource.actions.configs=resource-actions/default.xml
\end{verbatim}
Once you've defined portlet permissions, root model permissions, and
model permissions, you've completed step 1 (the \emph{D} in DRAC).
Congratulations! You're now ready to \emph{register} the resources
you've now defined in the permissions system.
\chapter{Registering Permissions}\label{registering-permissions}
Defining permissions was your first step; now you're ready to register
the permissions you've defined. You must register your entities both in
the database and in the permissions service running in the OSGi
container.
\section{Registering Permissions Resources in the
Database}\label{registering-permissions-resources-in-the-database}
All this takes is a call to Liferay's resource service in your service
layer. If you're using Service Builder, this is very easy to do.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your \texttt{-LocalServiceImpl} class.
\item
In your method that adds an entity, add a call to add a resource with
the entity. For example, Liferay's Blogs application adds resources
this way:
\begin{verbatim}
resourceLocalService.addResources(
entry.getCompanyId(), entry.getGroupId(), entry.getUserId(),
BlogsEntry.class.getName(), entry.getEntryId(), false,
addGroupPermissions, addGuestPermissions);
\end{verbatim}
This method requires passing in the company ID, the group ID, the user
ID, the entity's class name, the entity's primary key, and some
boolean settings. In order, these settings define
\begin{itemize}
\tightlist
\item
Whether the permission is a portlet resource
\item
Whether the default group permissions defined in
\texttt{default.xml} should be added
\item
Whether the default guest permissions defined in
\texttt{default.xml} should be added
\end{itemize}
\end{enumerate}
Note that the resource local service is injected automatically into your
Service Builder-generated service.
If you're not using Service Builder, but you are using OSGi modules for
your application, you should be able to inject the resource service with
an \texttt{@Reference} annotation. If you're building a WAR-style
plugin, you need a
\href{/docs/7-2/frameworks/-/knowledge_base/f/service-trackers-for-osgi-services}{service
tracker} to gain access to the service. Note that your model classes
must also implement Liferay's \texttt{ClassedModel} interface.
Similarly, when you delete an entity, you should also delete its
associated resource. Here's how the Blogs application does it in its
\texttt{deleteEntry()} method:
\begin{verbatim}
resourceLocalService.deleteResource(
entry.getCompanyId(), BlogsEntry.class.getName(),
ResourceConstants.SCOPE_INDIVIDUAL, entry.getEntryId());
\end{verbatim}
As with adding resources, the method needs to know the entity's company
ID, class, and primary key. Most of the time, its scope is an individual
entity of your own choosing. Other scopes available as constants are for
company, group, or group template (site template). These are used
internally for those objects, so you'd only use them if you were
customizing functionality for creating and deleting them.
Now you're ready to register your entities with the permissions service.
\section{Registering Entities to the Permissions
Service}\label{registering-entities-to-the-permissions-service}
The permissions service that's running must know about your entities and
how to check permissions for them. This requires creating a permissions
registrar class.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your service bundle, create a package that by convention ends in
\texttt{internal.security.permission.resource}. For example, the Blogs
application's package is named
\texttt{com.liferay.blogs.internal.security.permission.resource}.
\item
Create a class in this package called
\texttt{{[}Entity\ \ \ \ \ Name{]}ModelResourcePermissionRegistrar}.
For example, the Blogs application's class is named
\texttt{BlogsEntryModelResourcePermissionRegistrar}.
\item
This class is a component class that requires overriding the
\texttt{activate} method to register the permissions logic you want
for your entities. For example, this is how the Blogs application
registers its permissions:
\begin{verbatim}
@Component(immediate = true)
public class BlogsEntryModelResourcePermissionRegistrar {
@Activate
public void activate(BundleContext bundleContext) {
Dictionary properties = new HashMapDictionary<>();
properties.put("model.class.name", BlogsEntry.class.getName());
_serviceRegistration = bundleContext.registerService(
ModelResourcePermission.class,
ModelResourcePermissionFactory.create(
BlogsEntry.class, BlogsEntry::getEntryId,
_blogsEntryLocalService::getEntry, _portletResourcePermission,
(modelResourcePermission, consumer) -> {
consumer.accept(
new StagedModelPermissionLogic<>(
_stagingPermission, BlogsPortletKeys.BLOGS,
BlogsEntry::getEntryId));
consumer.accept(
new WorkflowedModelPermissionLogic<>(
_workflowPermission, modelResourcePermission,
BlogsEntry::getEntryId));
}),
properties);
}
@Deactivate
public void deactivate() {
_serviceRegistration.unregister();
}
@Reference
private BlogsEntryLocalService _blogsEntryLocalService;
@Reference(target = "(resource.name=" + BlogsConstants.RESOURCE_NAME + ")")
private PortletResourcePermission _portletResourcePermission;
private ServiceRegistration _serviceRegistration;
@Reference
private StagingPermission _stagingPermission;
@Reference
private WorkflowPermission _workflowPermission;
}
\end{verbatim}
\end{enumerate}
We call these types of classes Registrars because the classes' job is to
configure, register and unregister the \texttt{ModelResourcePermission}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The \texttt{model.class.name} is set in the properties so that other
modules' service trackers can find this model resource permission by
its type when it's needed. Liferay has several service trackers
checking for model resource permissions. The \texttt{service.ranking}
property can also be set to a value greater than zero to override
other module's model resource permissions.
\item
This registrar uses two portal-kernel permission logic classes for
Staging and Workflow. Custom logic classes can be reused or composed
inline since \texttt{ModelResourcePermissionLogic} is a
\texttt{@FunctionalInterface}. Permission logic classes are executed
in order of when they are accepted in the \texttt{Consumer}.
\item
\texttt{ModelResourcePermissionLogic} classes return \texttt{true}
when users have permission for the action, \texttt{false} when they
are denied permission for the action, and \texttt{null} when wanting
to delegate responsibility to the next permission logic. If all
permission logics return null then the
\texttt{PermissionChecker.hasPermission} method is called to determine
if the action is allowed for the user.
\end{enumerate}
This class uses an \texttt{@Reference} with the target filter to inject
the appropriate \texttt{PortletResourcePermission}.
\texttt{BlogsConstants.RESOURCE\_NAME} evaluates to
\texttt{com.liferay.blogs}, which is defined in the \texttt{default.xml}
you created earlier. If you were to reference this
\texttt{ModelResourcePermission}, you'd use a target filter matching the
\texttt{model.class.name} property set in the \texttt{activate} method.
Note that you specify your entity's class, primary key, and the entity
itself for the factory so it can create permission objects specific to
your entity.
Great! You've now completed step 2 in \emph{DRAC} by registering your
permissions. Now you're ready to provide users the interface to
associate permissions with resources.
\chapter{Associating Permissions with
Resources}\label{associating-permissions-with-resources}
Now that you've defined and registered permissions, you must expose the
permissions interface so users can set permissions.
To allow permissions to be configured for model resources, you must add
the permissions interface to the UI. Add these two Liferay UI tags to
your JSP:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\texttt{\textless{}liferay-security:permissionsURL\textgreater{}}:
Returns a URL to the permission settings configuration page.
\item
\texttt{\textless{}liferay-ui:icon\textgreater{}}: Shows an icon to
the user. These are defined in the theme and one of them (see below)
is used for permissions.
\end{enumerate}
The Blogs application uses these tags like this:
\begin{verbatim}
\end{verbatim}
For the
\texttt{\textless{}liferay-security:permissionsURL\ /\textgreater{}}
tag, specify these attributes:
\texttt{modelResource}: The fully qualified class name of the entity
class. This class name gets translated into a more readable name as
specified in \texttt{Language.properties}.
\texttt{Language.properties}: The entity class in the example above is
the Blogs entry class for which the fully qualified class name is
\texttt{com.liferay.blogs.model.BlogsEntry}.
\texttt{modelResourceDescription}: You can enter anything that best
describes this model instance. In the example above, the Blog title is
used for the model resource description.
\texttt{resourcePrimKey}: Your entity's primary key.
\texttt{var}: The name of the variable to which the resulting URL string
is assigned. The variable is then passed to the
\texttt{\textless{}liferay-ui:icon\textgreater{}} tag so the permission
icon has the proper URL link.
There's an optional attribute called \texttt{redirect} that's available
if you want to override the default behavior of the upper right arrow
link. That's it; now your users can configure the permission settings
for model resources!
You've completed step 3 in \emph{DRAC}. Your next step is to check for
permissions in the appropriate areas of your application.
\chapter{Checking Permissions}\label{checking-permissions}
Now that you've defined your permissions, registered resources in the
database and with the OSGi container, and enabled users to associate
permissions with resources, you're ready to add permission checks in the
appropriate places in your application. This takes three steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add permission checks to your service calls.
\item
Create permission helper classes in your web module.
\item
Add permission checks to your web application.
\end{enumerate}
These things are covered next.
\section{Add Permission Checks to Your Service
Calls}\label{add-permission-checks-to-your-service-calls}
A best practice is to create methods in your \texttt{-ServiceImpl}
classes that call the same methods in your \texttt{-LocalServiceImpl}
classes, but wrap those calls in permission checks. If you expose your
services as web services, then any client calling those services must
have permission to call the service. In this way, you separate your
business logic (contained in the \texttt{-LocalServiceImpl} class) from
your permissions logic (contained in the \texttt{-ServiceImpl} class).
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your entity's \texttt{-ServiceImpl} class.
\item
Use the \texttt{ModelResourcePermissionFactory} and the
\texttt{PortletResourcePermissionFactory} to reference permission
checkers that can check permissions as you've defined them in
\texttt{default.xml}. Here's how the Blogs portlet does this:
\begin{verbatim}
private static volatile ModelResourcePermission
_blogsEntryFolderModelResourcePermission =
ModelResourcePermissionFactory.getInstance(
BlogsEntryServiceImpl.class,
"_blogsEntryFolderModelResourcePermission", BlogsEntry.class);
private static volatile PortletResourcePermission
_portletResourcePermission =
PortletResourcePermissionFactory.getInstance(
BlogsEntryServiceImpl.class, "_portletResourcePermission",
BlogsConstants.RESOURCE_NAME);
\end{verbatim}
You declare the class, the variable, and for the portlet resource, the
resource name from \texttt{default.xml}. In the Blogs application,
\texttt{BlogsConstants.RESOURCE\_NAME} is a \texttt{String} with the
value \texttt{com.liferay.blogs}.
You must use \texttt{ModelResourcePermissionFactory.getInstance()} in
the service because Service Builder is wired with Spring, so
\texttt{@Reference} can't be used. Make sure to use the correct
service class and the name of the field that's being set (in this case
\texttt{"\_blogsEntryFolderModelResourcePermission"}), because it's
set with reflection when the service is registered. If you get the
field wrong, it'll be set wrong. The field must be \texttt{static} and
\texttt{volatile}, and should never be used outside of
\texttt{-ServiceImpl} classes.
\item
Check permissions in the appropriate places. For example, adding a
blog entry requires the \texttt{ADD\_ENTRY} permission, so the Blogs
application does this:
\begin{verbatim}
@Override
public BlogsEntry addEntry(
String title, String subtitle, String description, String content,
int displayDateMonth, int displayDateDay, int displayDateYear,
int displayDateHour, int displayDateMinute, boolean allowPingbacks,
boolean allowTrackbacks, String[] trackbacks,
String coverImageCaption, ImageSelector coverImageImageSelector,
ImageSelector smallImageImageSelector,
ServiceContext serviceContext)
throws PortalException {
_portletResourcePermission.check(
getPermissionChecker(), serviceContext.getScopeGroupId(),
ActionKeys.ADD_ENTRY);
return blogsEntryLocalService.addEntry(
getUserId(), title, subtitle, description, content,
displayDateMonth, displayDateDay, displayDateYear, displayDateHour,
displayDateMinute, allowPingbacks, allowTrackbacks, trackbacks,
coverImageCaption, coverImageImageSelector, smallImageImageSelector,
serviceContext);
}
\end{verbatim}
The check throws an exception if it fails, preventing the local
service call that adds the entry. A convention Liferay uses is to
place the action keys from \texttt{default.xml} as constants in an
\texttt{ActionKeys} class. If \texttt{ActionKeys} doesn't have an
action key appropriate for your application, extend Liferay's class
and add your own keys.
\end{enumerate}
Add permission checks where necessary to protect your application's
functions at the service level. Next, you'll learn how to create
permission helper classes for your web module.
\section{Create Permission Helper Classes in Your Web
Module}\label{create-permission-helper-classes-in-your-web-module}
A helper class can make it easier to check permissions in your portlet
application. You can create helper classes for both portlet permissions
and model permissions. Here's how to create a portlet permission helper:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a package with the suffix
\texttt{web.internal.security.permission.resource}. For example, the
Blogs application has the package
\texttt{com.liferay.blogs.web.internal.security.permission.resource}.
\item
Create a component class with at least one static method for checking
permissions. For example, here's the \texttt{BlogsPermission} class:
\begin{verbatim}
@Component(immediate = true)
public class BlogsPermission {
public static boolean contains(
PermissionChecker permissionChecker, long groupId, String actionId) {
return _portletResourcePermission.contains(
permissionChecker, groupId, actionId);
}
@Reference(
target = "(resource.name=" + BlogsConstants.RESOURCE_NAME + ")",
unbind = "-"
)
protected void setPortletResourcePermission(
PortletResourcePermission portletResourcePermission) {
_portletResourcePermission = portletResourcePermission;
}
private static PortletResourcePermission _portletResourcePermission;
}
\end{verbatim}
Note the \texttt{@Reference} annotation that tells the OSGi container
to supply an object via the permission registrar you created
previously. The \texttt{\_portletResourcePermission} field is static,
while the setter method is an instance method: this is how Liferay
avoids having service references in JSPs.
\end{enumerate}
The procedure for creating a model permission helper is similar:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the same package, create a component class with at least one static
method for checking permissions. For example, here's the
\texttt{BlogsEntryPermission} class:
\begin{verbatim}
@Component(immediate = true)
public class BlogsEntryPermission {
public static boolean contains(
PermissionChecker permissionChecker, BlogsEntry entry,
String actionId)
throws PortalException {
return _blogsEntryFolderModelResourcePermission.contains(
permissionChecker, entry, actionId);
}
public static boolean contains(
PermissionChecker permissionChecker, long entryId, String actionId)
throws PortalException {
return _blogsEntryFolderModelResourcePermission.contains(
permissionChecker, entryId, actionId);
}
@Reference(
target = "(model.class.name=com.liferay.blogs.model.BlogsEntry)",
unbind = "-"
)
protected void setEntryModelPermission(
ModelResourcePermission modelResourcePermission) {
_blogsEntryFolderModelResourcePermission = modelResourcePermission;
}
private static ModelResourcePermission
_blogsEntryFolderModelResourcePermission;
}
\end{verbatim}
As you can see, this class is almost the same as the portlet
permission class. The real difference is in the \texttt{@Reference}
annotation that specifies the fully qualified class name of the model,
rather than the resource name from \texttt{default.xml}.
\item
Save both files.
\end{enumerate}
Now you're ready to use these helper classes to check permissions in
your web module.
\section{Add Permission Checks to Your Web
Application}\label{add-permission-checks-to-your-web-application}
You can use the permission helper classes to check for permissions
before displaying UI elements. If the element never appears, a user
can't access it (though you should also protect your services as
described above). Here's how to do that:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
When you have a function you want to protect, wrap it in an
\texttt{if} statement that uses the permission helper class. For
example, the Blogs application has many functions protected by
permissions, including \texttt{ADD\_ENTRY} and \texttt{SUBSCRIBE}.
Clearly, only blog owners should be able to add blog entries. The
button for this, therefore, should only appear if a user has
permission to add entries:
\begin{verbatim}
\end{verbatim}
\item
Do this for any function. For example, the Permissions function you
added in
\href{/docs/7-2/frameworks/-/knowledge_base/f/associating-permissions-with-resources}{step
3} should definitely be protected by permissions:
\begin{verbatim}
\end{verbatim}
This prevents anyone without the permission to set permissions from
seeing the permissions button. Say that three times fast!
\end{enumerate}
That's all there is to it! You've now learned all the steps in
\emph{DRAC}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Define permissions
\item
Register permissions
\item
Associate permissions with resources
\item
Check permissions
\end{enumerate}
Follow these steps, and your applications can take advantage of
Liferay's integrated and well-tested permissions system.
\chapter{Using JSR Roles in a
Portlet}\label{using-jsr-roles-in-a-portlet}
Roles in Liferay DXP are the primary means for granting or restricting
access to content. If you've decided \emph{not} to use Liferay's
permissions system, you can use the basic system offered by the JSR 168,
286, and 362 specifications that map Roles in a portlet to Roles
provided by the portal.
\section{JSR Portlet Security}\label{jsr-portlet-security}
The portlet specification defines a means to specify Roles used by
portlets in their \texttt{docroot/WEB-INF/portlet.xml} descriptors. The
Role names themselves, however, are not standardized. When these
portlets run in Liferay DXP, the Role names defined in the portlet must
be mapped to Roles that exist in the Portal.
For example, consider a Guestbook project that contains two portlets:
The Guestbook portlet and the Guestbook Admin portlet. The WAR version
of the Guestbook project's \texttt{portlet.xml} file references the
\emph{administrator}, \emph{guest}, \emph{power-user}, and \emph{user}
Roles:
\begin{verbatim}
guestbook-war
guestbook-war
com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet
template-path
/
view-template
/view.jsp
0
text/html
content.Language
guestbook-war
guestbook-war
guestbook-war
administrator
guest
power-user
user
\end{verbatim}
An OSGi-based \texttt{guestbook-web} module project defines Roles
without an XML file, in the portlet class's \texttt{@Component}
annotation:
\begin{verbatim}
@Component(
immediate = true,
property = {
"com.liferay.portlet.display-category=category.sample",
"com.liferay.portlet.instanceable=true",
"javax.portlet.init-param.template-path=/",
"javax.portlet.init-param.view-template=/view.jsp",
"javax.portlet.name=" + GuestbookPortletKeys.Guestbook,
"javax.portlet.resource-bundle=content.Language",
"javax.portlet.security-role-ref=power-user,user"
},
service = Portlet.class
)
\end{verbatim}
If you are using an OSGi-based MVC Portlet, you must use Liferay's
permissions system, as the only way to map JSR-362 Roles to Liferay
Roles is to place them in the Liferay WAR file's \texttt{portlet.xml}.
Your \texttt{portlet.xml} Roles must be mapped to specific Roles that
have been created. These mappings allow Liferay DXP to resolve conflicts
between Roles with the same name that are from different portlets
(e.g.~portlets from different developers).
\noindent\hrulefill
\textbf{Note:} Each Role named in a portlet's
\texttt{\textless{}security-role-ref\textgreater{}} element is given
permission to add the portlet to a page.
\noindent\hrulefill
\section{Mapping Portlet Roles to Portal
Roles}\label{mapping-portlet-roles-to-portal-roles}
To map the Roles to Liferay DXP, you must use the
\texttt{docroot/WEB-INF/liferay-portlet.xml} Liferay-specific
configuration file. For an example, see the mapping defined in the
Guestbook project's \texttt{liferay-portlet.xml} file.
\begin{verbatim}
administrator
Administrator
guest
Guest
power-user
Power User
user
User
\end{verbatim}
If a portlet definition references the Role \texttt{power-user}, that
portlet is mapped to the Liferay Role called \emph{Power User} that's
already in Liferay's database.
As stated above, there is no standardization with portal Role names. If
you deploy a portlet with Role names different from the above default
Liferay names, you must add the names to the \texttt{system.roles}
property in your \texttt{portal-ext.properties} file:
\begin{verbatim}
system.roles=my-role,your-role,our-role
\end{verbatim}
This prevents Roles from being created accidentally.
Once Roles are mapped to the portal, you can use methods as defined in
the portlet specification:
\begin{itemize}
\tightlist
\item
\texttt{getRemoteUser()}
\item
\texttt{isUserInRole()}
\item
\texttt{getUserPrincipal()}
\end{itemize}
For example, you can use the following code to check if the current User
has the \texttt{power-user} Role:
\begin{verbatim}
if (renderRequest.isUserInRole("power-user")) {
// ...
}
\end{verbatim}
By default, Liferay doesn't use the \texttt{isUserInRole()} method in
any built-in portlets. Liferay uses its own permission system directly
to achieve more fine-grained security. If you don't intend on deploying
your portlets to other portal servers, we recommend using Liferay's
permission system, because it offers a much more robust way of tailoring
your application's permissions.
\section{Related Topics}\label{related-topics}
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{Liferay
Permissions}
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset
Framework}
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
\href{/docs/7-2/frameworks/-/knowledge_base/f/understanding-servicecontext}{Understanding
ServiceContext}
\chapter{Authentication Pipelines}\label{authentication-pipelines}
The authentication process is a pipeline through which users can be
validated by one or several systems. As a developer, you can
authenticate users to anything you wish, rather than be limited by what
Liferay DXP supports out of the box.
Here's how authentication works under most circumstances:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Users provide their credentials to the Login Portlet to begin an
authenticated session in a browser.
\item
Alternatively, credentials are provided to Liferay DXP's API
endpoints, where they are sent in an HTTP BASIC Auth header.
\item
Alternatively, credentials can be provided by another system. These
are managed by \texttt{AutoLogin} components.
\item
Credentials are checked by default against the database, but they can
be delegated to other systems instead of or in addition to it. This is
called an \emph{Authentication Pipeline}. You can add
\texttt{Authenticator}s to the pipeline to support any system.
\item
You can also customize the Login Portlet to support whatever user
interface any of these systems need. This gives you full flexibility
over the entire authentication process.
\end{enumerate}
This structure lets you support an authentication mechanism and/or
accept credentials from a system that Liferay DXP doesn't yet support.
If you don't like the user interface for signing in, you can replace it
with your own.
These tutorials guide you through these customizations. You'll discover
three kinds of customizations:
\begin{itemize}
\item
\textbf{Auto Login:} the easiest of the three, this enables
authentication to Liferay DXP using credentials provided in the HTTP
header from another system.
\item
\textbf{Authentication Pipelines:} if you must check credentials
against other systems instead of or in addition to Liferay DXP's
database, you can create a pipeline.
\item
\textbf{Custom Login Portlet:} if you want to change the user's
sign-in experience completely, you can implement your own Login
portlet.
\end{itemize}
Read on to discover how to customize your users' sign-in experience.
\chapter{Auto Login}\label{auto-login}
While Liferay DXP supports a wide variety of
\href{/docs/7-2/deploy/-/knowledge_base/d/securing-product}{authentication
mechanisms}, you may use a home-grown system or some other product to
authenticate users. To do so, you can write an Auto Login component to
support your authentication system.
Auto Login components can check if the request contains something (a
cookie, an attribute) that can be associated with a user in any way. If
the component can make that association, it can authenticate that user.
\section{Creating an Auto Login
Component}\label{creating-an-auto-login-component}
Create a
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Declarative
Services component}. The component should implement the
\texttt{com.liferay.portal.kernel.security.auto.login.AutoLogin}
interface. Here's an example template:
\begin{verbatim}
import com.liferay.portal.kernel.security.auto.login.AutoLogin;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.osgi.service.component.annotations.Component;
@Component(immediate = true)
public class MyAutoLogin implements Autologin {
public String[] handleException(
HttpServletRequest request, HttpServletResponse response,
Exception e)
throws AutoLoginException {
/* This method is no longer used in the interface and can be
left empty */
}
public String[] login(
HttpServletRequest request, HttpServletResponse response)
throws AutoLoginException {
/* Your Code Goes Here */
}
}
\end{verbatim}
As you can see, you have access to the \texttt{HttpServletRequest} and
the \texttt{HttpServletResponse} objects. If your sign-on solution
places anything here that identifies a user such as a cookie, an
attribute, or a parameter, you can retrieve it and take whatever action
you need to retrieve the user information and authenticate that user.
For example, say that there's a request attribute that contains the
encrypted value of a user key. This can only be there if the user has
authenticated with a third party system that knew the value of the user
key, encrypted it, and added it as a request attribute. You could write
code that reads the value, decrypts it using the same pre-shared key,
and uses the value to look up and authenticate the user.
The \texttt{login} method is where this all happens. This method must
return a \texttt{String} array with three items in this order:
\begin{itemize}
\tightlist
\item
The user ID
\item
The user password
\item
A boolean flag that's \texttt{true} if the password is encrypted and
\texttt{false} if it's not (\texttt{Boolean.TRUE.toString()} or
\texttt{Boolean.FALSE.toString()}).
\end{itemize}
Sending redirects is an optional \texttt{AutoLogin} feature. Since
\texttt{AutoLogin}s are part of the servlet filter chain, you have two
options. Both are implemented by setting attributes in the request. Here
are the attributes:
\begin{itemize}
\item
\texttt{AutoLogin.AUTO\_LOGIN\_REDIRECT}: This key causes
\texttt{AutoLoginFilter} to stop the filter chain's execution and
redirect immediately to the location specified in the attribute's
value.
\item
\texttt{AutoLogin.AUTO\_LOGIN\_REDIRECT\_AND\_CONTINUE}: This key
causes \texttt{AutoLoginFilter} to set the redirect and continue
executing the remaining filters in the chain.
\end{itemize}
Auto Login components are useful ways of providing an authentication
mechanism to a system that Liferay DXP doesn't yet support. You can
write them fairly quickly to provide the integration you need.
\section{Related Topics}\label{related-topics-1}
\href{/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines}{Password-Based
Authentication Pipelines}
\href{/docs/7-2/frameworks/-/knowledge_base/f/writing-a-custom-login-portlet}{Writing
a Custom Login Portlet}
\chapter{Password-Based Authentication
Pipelines}\label{password-based-authentication-pipelines}
By default, once a user submits credentials, those credentials are
checked against Liferay DXP's database, though you can also delegate
authentication to an LDAP server. To use some other system in your
environment instead of or in addition to checking credentials against
the database, you can write an \texttt{Authenticator} and insert it as a
step in the authentication pipeline.
Because the \texttt{Authenticator} is checked by the Login Portlet, you
can't use this approach if the user must be redirected to the external
system or needs a token to authenticate. In those cases, you should use
an \href{/docs/7-2/frameworks/-/knowledge_base/f/auto-login}{Auto Login}
or an
\href{/docs/7-2/deploy/-/knowledge_base/d/authentication-verifiers}{Auth
Verifier}.
\texttt{Authenticator}s let you do these things:
\begin{itemize}
\tightlist
\item
Log into Liferay DXP with a user name and password maintained in an
external system
\item
Make secondary user authentication checks
\item
Perform additional processing when user authentication fails
\end{itemize}
Read on to learn how to create an \texttt{Authenticator}.
\section{Anatomy of an Authenticator}\label{anatomy-of-an-authenticator}
\texttt{Authenticator}s are implemented for various steps in the
authentication pipeline. Here are the steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\texttt{auth.pipeline.pre}: Comes before default authentication to the
database. In this step, you can skip credential validation against the
database. Implemented by \texttt{Authenticator}.
\item
Default (optional) authentication to the database.
\item
\texttt{auth.pipeline.post}: Further (secondary, tertiary)
authentication checks. Implemented by \texttt{Authenticator}.
\item
\texttt{auth.failure}: Perform additional processing after
authentication fails. Implemented by \texttt{AuthFailure}.
\end{enumerate}
To create an \texttt{Authenticator}, create a module and add a component
that implements the interface:
\begin{verbatim}
@Component(
immediate = true, property = {"key=auth.pipeline.post"},
service = Authenticator.class
)
public class MyCustomAuth implements Authenticator {
public int authenticateByEmailAddress(
long companyId, String emailAddress, String password,
Map headerMap, Map parameterMap)
throws AuthException {
return Authenticator.SUCCESS;
}
public int authenticateByScreenName(
long companyId, String screenName, String password,
Map headerMap, Map parameterMap)
throws AuthException {
return Authenticator.SUCCESS;
}
public int authenticateByUserId(
long companyId, long userId, String password,
Map headerMap, Map parameterMap)
throws AuthException {
return Authenticator.SUCCESS;
}
}
\end{verbatim}
This example has been stripped down so you can see its structure. First,
note the \texttt{@Component} annotation's contents:
\begin{itemize}
\tightlist
\item
\texttt{immediate\ =\ true}: sets the component to start immediately
\item
\texttt{key=auth.pipeline.post}: sets the \texttt{Authenticator} to
run in the \texttt{auth.pipeline.post} phase. To run the
\texttt{auth.pipeline.pre} phase, substitute
\texttt{auth.pipeline.pre}.
\item
\texttt{service\ =\ Authenticator.class}: implements the
\texttt{Authenticator} service. All \texttt{Authenticator}s must do
this.
\end{itemize}
The three methods below the annotation run based on how you've
configured authentication: by email address (the default), by screen
name, or by user ID. All the methods throw an \texttt{AuthException} in
case the \texttt{Authenticator} can't perform its task: if the system
it's authenticating against is unavailable or if some dependency can't
be found. The methods in this barebones example return success in all
cases. If you deploy its module, it has no effect. Naturally, you'll
want to provide more functionality. Next is an example that shows you
how to do that.
\section{Creating an Authenticator}\label{creating-an-authenticator}
This example is an \texttt{Authenticator} that only allows users whose
email addresses end with \emph{@liferay.com} or \emph{@example.com}. You
can implement this using one module that does everything. If you think
other modules might use the functionality that validates the email
addresses, you should create two modules: one to implement the
\texttt{Authenticator} and one to validate email addresses. This example
shows the two module approach.
To create an \texttt{Authenticator}, create a module for your
implementation. The most appropriate Blade template for this is the
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-service-template}{service
template}. Once you have the module, creating the \texttt{Activator} is
straightforward:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{@Component} annotation to bind your \texttt{Activator}
to the appropriate authentication pipeline phase.
\item
Implement the \texttt{Authenticator} interface and provide the
functionality you need.
\item
Deploy your module. If you're using
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}, do
this via \texttt{blade\ deploy}.
\end{enumerate}
For this example, you'll do this twice: once for the email address
validator module and once for the \texttt{Authenticator} itself. The
\texttt{Authenticator} project contains the interface for the validator,
and the validator project contains the implementation. Here's what the
\texttt{Authenticator} module structure looks like:
\begin{figure}
\centering
\includegraphics{./images/auth-pipeline-authenticator-project.png}
\caption{The Authenticator module contains the validator's interface and
the authenticator.}
\end{figure}
Since the \texttt{Authenticator} is the most relevant, examine it first:
\begin{verbatim}
package com.liferay.docs.emailaddressauthenticator;
import java.util.Map;
import com.liferay.docs.emailaddressauthenticator.validator.EmailAddressValidator;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.security.auth.AuthException;
import com.liferay.portal.kernel.security.auth.Authenticator;
import com.liferay.portal.kernel.service.UserLocalService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
@Component(
immediate = true,
property = {"key=auth.pipeline.post"},
service = Authenticator.class
)
public class EmailAddressAuthenticator implements Authenticator {
@Override
public int authenticateByEmailAddress(long companyId, String emailAddress,
String password, Map headerMap,
Map parameterMap) throws AuthException {
return validateDomain(emailAddress);
}
@Override
public int authenticateByScreenName(long companyId, String screenName,
String password, Map headerMap,
Map parameterMap) throws AuthException {
String emailAddress =
_userLocalService.fetchUserByScreenName(companyId, screenName).getEmailAddress();
return validateDomain(emailAddress);
}
@Override
public int authenticateByUserId(long companyId, long userId,
String password, Map headerMap,
Map parameterMap) throws AuthException {
String emailAddress =
_userLocalService.fetchUserById(userId).getEmailAddress();
return validateDomain(emailAddress);
}
private int validateDomain(String emailAddress) throws AuthException {
if (_emailValidator == null) {
String msg = "Email address validator is unavailable, cannot authenticate user";
_log.error(msg);
throw new AuthException(msg);
}
if (_emailValidator.isValidEmailAddress(emailAddress)) {
return Authenticator.SUCCESS;
}
return Authenticator.FAILURE;
}
@Reference
private volatile UserLocalService _userLocalService;
@Reference(
policy = ReferencePolicy.DYNAMIC,
cardinality = ReferenceCardinality.OPTIONAL
)
private volatile EmailAddressValidator _emailValidator;
private static final Log _log = LogFactoryUtil.getLog(EmailAddressAuthenticator.class);
}
\end{verbatim}
This time, rather than stubs, the three authentication methods contain
functionality. The \texttt{authenticateByEmailAddress} method directly
checks the email address provided by the Login Portlet. The other two
methods, \texttt{authenticateByScreenName} and
\texttt{authenticateByUserId} call \texttt{UserLocalService} to look up
the user's email address before checking it. The OSGi container injects
this service because of the \texttt{@Reference} annotation. Note that
the validator is also injected in this same manner, though it's
configured not to fail if the implementation can't be found. This allows
this module to start regardless of its dependency on the validator
implementation. In this case, this is safe because the error is handled
by throwing an \texttt{AuthException} and logging the error.
Why would you want to do it this way? To err gracefully. Because this is
an \texttt{auth.pipeline.post} \texttt{Authenticator}, you presumably
have other \texttt{Authenticator}s checking credentials before this one.
If this one isn't working, you want to inform administrators with an
error message rather than catastrophically failing and preventing users
from logging in.
The only other Java code in this module is the Interface for the
validator:
\begin{verbatim}
package com.liferay.docs.emailaddressauthenticator.validator;
import aQute.bnd.annotation.ProviderType;
@ProviderType
public interface EmailAddressValidator {
public boolean isValidEmailAddress(String emailAddress);
}
\end{verbatim}
This defines a single method for checking the email address.
Next, you'll address the validator module.
\begin{figure}
\centering
\includegraphics{./images/auth-pipeline-validator-project.png}
\caption{The validator project implements the Validator Interface and
depends on the authenticator module.}
\end{figure}
This module contains only one class. It implements the Validator
interface:
\begin{verbatim}
package com.liferay.docs.emailaddressvalidator.impl;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.osgi.service.component.annotations.Component;
import com.liferay.docs.emailaddressauthenticator.validator.EmailAddressValidator;
@Component(
immediate = true,
property = {
},
service = EmailAddressValidator.class
)
public class EmailAddressValidatorImpl implements EmailAddressValidator {
@Override
public boolean isValidEmailAddress(String emailAddress) {
if (_validEmailDomains.contains(
emailAddress.substring(emailAddress.indexOf('@')))) {
return true;
}
return false;
}
private Set _validEmailDomains =
new HashSet(Arrays.asList(new String[] {"@liferay.com", "@example.com"}));
}
\end{verbatim}
This code checks to make sure that the email address is from the
\emph{@liferay.com} or \emph{@example.com} domains. The only other
interesting part of this module is the Gradle build script, because it
defines a compile-only dependency between the two projects. This is
divided into two files: a \texttt{settings.gradle} and a
\texttt{build.gradle}.
The \texttt{settings.gradle} file defines the location of the project
(the \texttt{Authenticator}) the validator depends on:
\begin{verbatim}
include ':emailAddressAuthenticator'
project(':emailAddressAuthenticator').projectDir = new File(settingsDir, '../com.liferay.docs.emailAddressAuthenticator')
\end{verbatim}
Since this project contains the interface, it must be on the classpath
at compile time, which is when \texttt{build.gradle} is running:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins", version: "3.0.23"
}
repositories {
mavenLocal()
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.plugin"
dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
compileOnly group: "org.osgi", name: "org.osgi.compendium", version: "5.0.0"
compileOnly project(":emailAddressAuthenticator")
}
repositories {
mavenLocal()
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
Note the line in the dependencies section that refers to the
\texttt{Authenticator} project defined in \texttt{settings.gradle}.
When these projects are deployed, the \texttt{Authenticator} you defined
runs, enforcing logins for the two domains specified in the validator.
\section{Related Topics}\label{related-topics-2}
\href{/docs/7-2/frameworks/-/knowledge_base/f/auto-login}{Auto Login}
\href{/docs/7-2/frameworks/-/knowledge_base/f/writing-a-custom-login-portlet}{Writing
a Custom Login Portlet}
\chapter{Writing a Custom Login
Portlet}\label{writing-a-custom-login-portlet}
If you need to customize your users' authentication experience
completely, you can write your own Login Portlet. The mechanics of this
on the macro level are no different from writing any other portlet, so
if you need to familiarize yourself with that, please see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{portlets}.
This tutorial shows only the relevant parts of a
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC Portlet} that authenticates the user. You'll learn how to call the
\href{/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines}{authentication
pipeline} and then redirect the user to a location of your choice.
\section{Authenticating to Liferay
DXP}\label{authenticating-to-liferay-dxp}
\noindent\hrulefill
\textbf{Note:} When developing a login portlet, set the session timeout
portal property like this:
\begin{verbatim}
session.timeout.auto.extend.offset=45
\end{verbatim}
This is needed because the default (as of
\href{https://issues.liferay.com/browse/LPS-68543}{LPS-68543}) setting
is \texttt{0}, causing the browser to execute an
\texttt{extend\_session} call. This may force users attempting to log in
to make the attempt twice.
\noindent\hrulefill
It has only one view, which is used for logging in or showing the user
who is already logged in:
\begin{verbatim}
<%@ include file="/init.jsp" %>
<%
String signedInAs = HtmlUtil.escape(user.getFullName());
if (themeDisplay.isShowMyAccountIcon() && (themeDisplay.getURLMyAccount() != null)) {
String myAccountURL = String.valueOf(themeDisplay.getURLMyAccount());
signedInAs = "" + signedInAs + " ";
}
%>
<%
String redirect = ParamUtil.getString(request, "redirect");
%>
\end{verbatim}
Note that in the form, authentication by email address (the default
setting) is hard-coded, as this is an example project. The current page
is sent as a hidden field on the form so the portlet can redirect the
user to it, but you can of course set this to any value you want.
The portlet handles all processing of this form using a single Action
Command (imports left out for brevity):
\begin{verbatim}
@Component(
property = {
"javax.portlet.name=MyLoginPortlet",
"mvc.command.name=/login/login"
},
service = MVCActionCommand.class
)
public class MyLoginMVCActionCommand extends BaseMVCActionCommand {
@Override
protected void doProcessAction(ActionRequest actionRequest,
ActionResponse actionResponse) throws Exception {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
WebKeys.THEME_DISPLAY);
HttpServletRequest request = PortalUtil.getOriginalServletRequest(
PortalUtil.getHttpServletRequest(actionRequest));
HttpServletResponse response = PortalUtil.getHttpServletResponse(
actionResponse);
String login = ParamUtil.getString(actionRequest, "login");
String password = actionRequest.getParameter("password");
boolean rememberMe = ParamUtil.getBoolean(actionRequest, "rememberMe");
String authType = CompanyConstants.AUTH_TYPE_EA;
AuthenticatedSessionManagerUtil.login(
request, response, login, password, rememberMe, authType);
actionResponse.sendRedirect(themeDisplay.getPathMain());
}
}
\end{verbatim}
The only tricky/unusual code here is the need to grab the
\texttt{HttpServletRequest} and the \texttt{HttpServletResponse}. This
is necessary to call Liferay DXP's API for authentication. At the end of
the Action Command, the portlet sends a redirect that sends the user to
the same page. You can of course make this any page you want.
Implementing your own login portlet gives you complete control over the
authentication process.
\section{Related Topics}\label{related-topics-3}
\href{/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines}{Password-Based
Authentication Pipelines}
\href{/docs/7-2/frameworks/-/knowledge_base/f/auto-login}{Auto Login}
\chapter{Service Access Policies}\label{service-access-policies}
Service access policies provide web service security beyond user
authentication to remote services. Together with
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{permissions},
service access policies limit remote service access by remote client
applications. This forms an additional security layer that protects user
data from unauthorized access and modification.
To connect to a web service, remote clients must authenticate using
credentials in that instance. This grants the remote client the
permissions assigned to those credentials in the Liferay DXP
installation. Service access policies further limit the remote client's
access to the services specified in the policy. Without such policies,
authenticated remote clients are treated like users: they can call any
remote API and read or modify data on behalf of the authenticated user.
Since remote clients are often intended for a specific use case,
granting them access to everything the user has permissions for poses a
security risk.
For example, consider a mobile app (client) that displays a user's
appointments from the Liferay Calendar app. This client app doesn't need
access to the API that updates the user profile, even though the user
has such permissions on the server. The client app doesn't even need
access to the Calendar API methods that create, update, and delete
appointments. It only needs access to the remote service methods for
finding and retrieving appointments. A service access policy on the
server can restrict the client's access to only these service methods.
Since the client doesn't perform other operations, having access to them
is a security risk if the mobile device is lost or stolen or the client
app is compromised by an attacker.
\section{How Service Access Policies
Work}\label{how-service-access-policies-work}
A remote client's request to a web service contains the user's
credentials or an authorization token. An authentication module
recognizes the client based on the credentials/token and grants the
appropriate service access policy to the request. The service access
policy authorization layer then processes all granted policies and lets
the request access the remote service(s) permitted by the policy.
\begin{figure}
\centering
\includegraphics{./images/service-access-policies-arch.png}
\caption{The authorization module maps the credentials or token to the
proper Service Access Policy.}
\end{figure}
Service Access policies are created in the Control Panel by
administrators. If you want to start creating policies yourself, see
\href{/docs/7-2/deploy/-/knowledge_base/d/service-access-policies}{this
article on service access policies} that documents creating them in the
UI.
There may be cases, however, when your server-side Liferay app must use
the service access policies API:
\begin{itemize}
\item
It uses
\href{/docs/7-2/frameworks/-/knowledge_base/f/auto-login}{custom
remote API authentication} (tokens) and require certain services to be
available for clients using the tokens.
\item
It requires its services be made available to guest users, with no
authentication necessary.
\item
It contains a
\href{/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines}{remote
service authorization layer} that needs to drive access to remote
services based on granted privileges.
\end{itemize}
\section{API Overview}\label{api-overview}
Liferay provides an Interface and a \texttt{ThreadLocal} if you don't
want to roll your own policies. If you want to get low level, an API is
provided that Liferay itself has used to implement
\href{/docs/7-2/user/-/knowledge_base/u/administering-liferay-sync}{Liferay
Sync}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The Interface and \texttt{ThreadLocal} are available in the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/service/access/policy/package-summary.html}{package
\texttt{com.liferay.portal.kernel.security.service.access.policy}}.
This package provides classes for basic access to policies. For
example, you can use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/service/access/policy/ServiceAccessPolicyManagerUtil.html}{singleton
\texttt{ServiceAccessPolicyManagerUtil}} to obtain Service Access
Policies configured in the system. You can also use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/service/access/policy/ServiceAccessPolicyThreadLocal.html}{\texttt{ServiceAccessPolicyThreadLocal}
class} to set and obtain Service Access Policies granted to the
current request thread.
At this level, you can get a list of the configured policies to let
your app/client choose a policy for accessing services. Also, apps
like OAuth can offer a list of available policies during the
authorization step in the OAuth workflow and allow the user to choose
the policy to assign to the remote application. You can also grant a
policy to a current request thread. When a remote client accesses an
API, something must tell the Liferay instance which policies are
assigned to this call. This something is in most cases an
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/auth/verifier/AuthVerifier.html}{\texttt{AuthVerifier}
implementation}. For example, in the case of the OAuth app, an
\texttt{AuthVerifier} implementation assigns the policy chosen by the
user in the authorization step.
\item
The API ships with the product as OSGi modules:
\end{enumerate}
\begin{itemize}
\item
\texttt{com.liferay.portal.security.service.access.policy.api.jar}
\item
\texttt{com.liferay.portal.security.service.access.policy.service.jar}
\item
\texttt{com.liferay.portal.security.service.access.policy.web.jar}
These OSGi modules are active by default, and you can use them to
manage Service Access Policies programmatically. Each module publishes
a list of packages and services that can be consumed by other OSGi
modules.
\end{itemize}
You can use both tools to develop a token verification module (a module
that implements custom security token verification for use in
authorizing remote clients) for your app to use. For example, this
module may contain a JSON Web Token implementation for Liferay DXP's
remote API. A custom token verification module must use the Service
Access Policies API during the remote API/web service call to grant the
associated policy during the request. The module
\begin{itemize}
\item
can use
\texttt{com.liferay.portal.security.service.access.policy.api.jar} and
\texttt{com.liferay.portal.security.service.access.policy.service.jar}
to create policies programmatically.
\item
should use the method
\texttt{ServiceAccessPolicyThreadLocal.addActiveServiceAccessPolicyName()}
to grant the associated policy during a web service request.
\item
can use \texttt{ServiceAccessPolicyManagerUtil} to display list of
supported policies when authorizing the remote application, to
associate the token with an existing policy.
\end{itemize}
\section{Service Access Policy
Example}\label{service-access-policy-example}
\href{https://www.liferay.com/supporting-products/liferay-sync}{Liferay
Sync's} \texttt{sync-security} module is a service access policy module.
It uses
\texttt{com.liferay.portal.security.service.access.policy.service} to
create the \texttt{SYNC\_DEFAULT} and \texttt{SYNC\_TOKEN} policies
programmatically. For service calls to Sync's remote API, these policies
grant access to Sync's
\texttt{com.liferay.sync.service.SyncDLObjectService\#getSyncContext}
and \texttt{com.liferay.sync.service.*}, respectively. Here's the code
in the \texttt{sync-security} module that defines and creates these
policies:
\begin{verbatim}
@Component(immediate = true)
public class SyncSAPEntryActivator {
// Define the policies
public static final Object[][] SAP_ENTRY_OBJECT_ARRAYS = new Object[][] {
{
"SYNC_DEFAULT",
"com.liferay.sync.service.SyncDLObjectService#getSyncContext", true
},
{"SYNC_TOKEN", "com.liferay.sync.service.*", false}
};
...
// Create the policies
protected void addSAPEntry(long companyId) throws PortalException {
for (Object[] sapEntryObjectArray : SAP_ENTRY_OBJECT_ARRAYS) {
String name = String.valueOf(sapEntryObjectArray[0]);
String allowedServiceSignatures = String.valueOf(
sapEntryObjectArray[1]);
boolean defaultSAPEntry = GetterUtil.getBoolean(
sapEntryObjectArray[2]);
SAPEntry sapEntry = _sapEntryLocalService.fetchSAPEntry(
companyId, name);
if (sapEntry != null) {
continue;
}
Map map = new HashMap<>();
map.put(LocaleUtil.getDefault(), name);
_sapEntryLocalService.addSAPEntry(
_userLocalService.getDefaultUserId(companyId),
allowedServiceSignatures, defaultSAPEntry, true, name, map,
new ServiceContext());
}
}
...
}
\end{verbatim}
This class creates the policies when the module starts. Note that this
module is included and enabled by default. You can access these and
other policies in \emph{Control Panel} → \emph{Configuration} →
\emph{Service Access Policy}.
The \texttt{sync-security} module must then grant the appropriate policy
when needed. Since every authenticated call to Liferay Sync's remote API
requires access to \texttt{com.liferay.sync.service.*}, the module must
grant the \texttt{SYNC\_TOKEN} policy to such calls. The module does
this with the method
\texttt{ServiceAccessPolicyThreadLocal.addActiveServiceAccessPolicyName},
as shown in this code snippet:
\begin{verbatim}
if ((permissionChecker != null) && permissionChecker.isSignedIn()) {
ServiceAccessPolicyThreadLocal.addActiveServiceAccessPolicyName(
String.valueOf(
SyncSAPEntryActivator.SAP_ENTRY_OBJECT_ARRAYS[1][0]));
}
\end{verbatim}
Now every authenticated call to Sync's remote API, regardless of
authentication method, has access to
\texttt{com.liferay.sync.service.*}. To see the full code example,
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/sync/sync-security/src/main/java/com/liferay/sync/security/servlet/filter/SyncAuthFilter.java}{click
here}.
Nice! Now you know how to integrate your apps with the Service Access
Policies.
\chapter{Frameworks}\label{frameworks}
To make your applications more fully featured and to develop them
faster, you can make use of Liferay's development frameworks. These help
you create commonly used features---like search, tagging, and
comments---without having to develop them from scratch. And since these
features are tried and tested, you can rest assured knowing they're bug
free.
Here are just a few frameworks you'll find here:
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{\textbf{A
fully-fledged permissions system:}} Implement permissions the way
they're implemented with the applications that ship with Liferay DXP for
a consistent, seamless, and robust experience.
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{\textbf{Assets:}}
Publish data from your application across the system, making it
available to those who need it, and enabling other features like
tagging, categorizing, and comments.
\href{/docs/7-2/frameworks/-/knowledge_base/f/search}{\textbf{Search:}}
If you have a data-driven application, you can add search capabilities
by integrating with Liferay's search indexer.
\href{/docs/7-2/frameworks/-/knowledge_base/f/configurable-applications}{\textbf{A
configuration system with auto-generated or custom UI:}} Are you
providing user-configurable options in your application? Make use of
Liferay's configuration system and provide a clean and consistent
experience for your users.
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{\textbf{File
management:}} Will your applications work with files? Use Liferay's
Documents and Media API to manage them.
\href{/docs/7-2/frameworks/-/knowledge_base/f/content-publication-management}{\textbf{Import/Export:}}
Use Liferay's import/export system to make your application's data
portable or to stage it for publication to production systems.
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{\textbf{Web
Fragments:}} Provide your content managers with dynamic chunks of
functionality they can use as building blocks for web pages.
\href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{\textbf{Workflow:}}
Run the data from your application through an approval process.
This really just scratches the surface. From
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{pop-up list
selectors} to a
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api}{social
networking API}, as a Liferay developer, you have access to tons of
frameworks that make your life easier.
\chapter{Asset Framework}\label{asset-framework}
The asset framework is behind many of Liferay's most powerful features.
It provides tools for displaying and interacting with various types of
content and data. For example, if you build an event management
application that displays a list of upcoming events, you can use the
asset framework to let users add tags, categories, or comments to make
entries more self-descriptive. Using the asset framework is also the
first step for integrating other important frameworks like Segmentation
and Personalization or Workflow.
As background, the term \emph{asset} refers to any type of content:
text, a file, a URL, an image, documents, blog entries, bookmarks, wiki
pages, or anything you create in your applications.
The asset framework tutorials assume that you've used Liferay's Service
Builder to generate your persistence layer, that you've implemented
permissions on the entities that you're persisting, and that you've
enabled them for search and indexing. You can learn more about Liferay's
Service Builder and how to use it in the
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} tutorial section. After that is completed, you can get started
asset enabling your application.
This section explores how to leverage the asset framework's various
features. Here are some features that you'll give your users as you
implement them in your app:
\begin{itemize}
\tightlist
\item
Extensively render your assets.
\item
Associate tags to custom content types. Users can create and assign
new tags or use existing tags.
\item
Associate categories to custom content types.
\item
Manage tags from the Control Panel. Administrators can even merge
tags.
\item
Manage categories from the Control Panel. This includes the ability to
create category hierarchies.
\item
Relate assets to one another.
\end{itemize}
There are several steps to creating an asset and taking full advantage
of the asset framework.
\section{Persistence Operations for
Assets}\label{persistence-operations-for-assets}
To use Liferay's asset framework with an entity, you must inform the
asset framework about each entity instance you create, modify, and
delete. In this sense, it's similar to informing
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{Liferay's
permissions framework} about a new resource. All you have to do is
invoke a method of the asset framework that associates an
\texttt{AssetEntry} with the entity so Liferay can keep track of the
entity as an asset. When it's time to update the entity, you update the
asset at the same time.
To leverage assets, you must also implement indexers for your portlet's
entities. Liferay's asset framework uses indexers to manage assets.
\section{Rendering an Asset}\label{rendering-an-asset}
Once you add your asset to the framework, you can render the asset using
the Asset Publisher application. The default render, however, only
displays the asset's title and description text. Anything else requires
additional coding. For instance, you might want these additional things:
\begin{itemize}
\tightlist
\item
An edit feature for modifying an asset.
\item
View an asset in its original context (e.g., a blog in the Blogs
application; a post in the Message Boards application).
\item
Embed images, videos, and audio.
\item
Restrict access to users who do not have permissions to interact with
the asset.
\item
Allow users to comment on the asset.
\end{itemize}
You can dictate your asset's rendering capabilities by providing the
\emph{Asset Renderer} framework. There are two prerequisites for asset
enabling an application:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The application must store asset data. Applications that store a data
model meet this requirement.
\item
The application must contain at least one non-instanceable portlet.
\texttt{Edit} links for the asset cannot be generated without a
non-instanceable portlet.
\end{enumerate}
Some applications may consist of only one non-instanceable portlet,
while others may consist of a both instanceable and non-instanceable
portlets. If your application does not currently include a
non-instanceable portlet, adding a configuration interface through a
panel app both enhances the usability of the application, and meets the
requirement for adding a non-instanceable portlet to the application.
After you have met all the prerequisites, there are two things you must
do to get your asset renderer functioning properly for your asset:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an asset renderer for your custom asset.
\item
Create an asset renderer factory to create an instance of the asset
renderer for each asset entity.
\end{enumerate}
\section{Asset Features}\label{asset-features}
Once you have done the necessary work to persist your assets and render
them, you can enable Tags, Categories, and Related Assets.
\section{Tags and Categories}\label{tags-and-categories}
Tags and Categories are two ways that you can organize and connect
assets. Tags are simple \emph{ad hoc} groups. Any two assets with the
same tag are connected by that tag. Categories are a form of
hierarchical organization where an administrator can define a number of
categories for organization content, images, or other types of assets
and use those categories to help users find what they're looking for.
\begin{figure}
\centering
\includegraphics{./images/asset-fw-categories-and-tags-options.png}
\caption{Adding category and tag input options lets authors aggregate
and label custom entities.}
\end{figure}
\section{Relating Assets}\label{relating-assets}
Relating assets connects individual pieces of content across your site
or portal. This helps users discover related content, particularly when
there's an abundance of other available content. For example, assets
related to a web content article appear alongside that entry in the
Asset Publisher application.
\begin{figure}
\centering
\includegraphics{./images/asset-related-content-asset-publisher.png}
\caption{You and your users can find it helpful to relate assets to
entities, such as this blogs entry.}
\end{figure}
\section{Implementing Asset Priority}\label{implementing-asset-priority}
The \href{/docs/7-2/user/-/knowledge_base/u/publishing-assets}{Asset
Publisher} lets you order assets by priority. For this to work, however,
users must be able to set the asset's priority when creating or editing
the asset. For example, when creating or editing web content, users can
assign a priority in the Metadata section's Priority field.
\begin{figure}
\centering
\includegraphics{./images/web-content-categorization.png}
\caption{The Priority field lets users set an asset's priority.}
\end{figure}
Ready to implement assets? The rest of the tutorials show you how.
\chapter{Adding, Updating, and Deleting
Assets}\label{adding-updating-and-deleting-assets}
This section shows you how to enable assets for your custom entities and
implement indexes for them. It's time to get started!
\section{Preparing Your Project for the Asset
Framework}\label{preparing-your-project-for-the-asset-framework}
In your project's \texttt{service.xml} file, add an asset entry entity
reference for your custom entity. Add the following \texttt{reference}
tag before your custom entity's closing
\texttt{\textless{}/entity\textgreater{}} tag.
\begin{verbatim}
\end{verbatim}
Then \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{run
Service Builder.}
Now you're ready to implement adding and updating assets!
\section{Adding and Updating Assets}\label{adding-and-updating-assets}
Your \texttt{-LocalServiceImpl} Java class inherits from its parent base
class an \texttt{AssetEntryLocalService} instance; it's assigned to the
variable \texttt{assetEntryLocalService}. To add your custom entity as a
Liferay asset, you must invoke the \texttt{assetEntryLocalService}'s
\texttt{updateEntry} method.
Here's what the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portlet/asset/service/impl/AssetEntryLocalServiceImpl.html\#updateEntry-long-long-java.util.Date-java.util.Date-java.lang.String-long-java.lang.String-long-long:A-java.lang.String:A-boolean-boolean-java.util.Date-java.util.Date-java.util.Date-java.util.Date-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-int-int-java.lang.Double-}{\texttt{updateEntry}}
method's signature looks like:
\begin{verbatim}
AssetEntry updateEntry(
long userId, long groupId, Date createDate, Date modifiedDate,
String className, long classPK, String classUuid, long classTypeId,
long[] categoryIds, String[] tagNames, boolean listable,
boolean visible, Date startDate, Date endDate, Date publishDate,
Date expirationDate, String mimeType, String title,
String description, String summary, String url, String layoutUuid,
int height, int width, Double priority)
throws PortalException
\end{verbatim}
Here are descriptions of each of the \texttt{updateEntry} method's
parameters:
\texttt{userId}: identifies the user updating the content.
\texttt{groupId}: identifies the scope of the created content. If your
content doesn't support scopes (extremely rare), pass \texttt{0} as the
value.
\texttt{createDate}: the date the entity was created.
\texttt{modifiedDate}: the date of this change to the entity.
\texttt{className}: identifies the entity's class. The recommended
convention is to use the name of the Java class that represents your
content type. For example, you can pass in the value returned from
\texttt{{[}YourClassName{]}.class.getName()}.
\texttt{classPK}: identifies the specific entity instance,
distinguishing it from other instances of the same type. It's usually
the primary key of the table where the entity is stored.
\texttt{classUuid}: serves as a secondary identifier that's guaranteed
to be universally unique. It correlates entity instances across scopes.
It's especially useful if your content is exported and imported across
separate portals.
\texttt{classTypeId}: identifies the particular variation of this class,
if it has any variations. Otherwise, use \texttt{0}.
\texttt{categoryIds}: represent the categories selected for the entity.
The asset framework stores them for you.
\texttt{tagNames}: represent the tags selected for the entity. The asset
framework stores them for you.
\texttt{listable}: specifies whether the entity can be shown in dynamic
lists of content (such as asset publisher configured dynamically).
\texttt{visible}: specifies whether the entity is approved.
\texttt{startDate}: the entity's publish date. You can use it to specify
when an Asset Publisher should show the entity's content.
\texttt{endDate}: the date the entity is taken down. You can use it to
specify when an Asset Publisher should stop showing the entity's
content.
\texttt{publishDate}: the date the entity will start to be shown.
\texttt{expirationDate}: the date the entity will no longer be shown.
\texttt{mimetype}: the Multi-Purpose Internet Mail Extensions type, such
as
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ContentTypes.html\#TEXT_HTML}{ContentTypes.TEXT\_HTML},
used for the content.
\texttt{title}: the entity's name.
\texttt{description}: a \texttt{String}-based textual description of the
entity.
\texttt{summary}: a shortened or truncated sample of the entity's
content.
\texttt{url}: a URL to optionally associate with the entity.
\texttt{layoutUuid}: the universally unique ID of the layout of the
entry's default display page.
\texttt{height}: this can be set to \texttt{0}.
\texttt{width}: this can be set to \texttt{0}.
\texttt{priority}: specifies how the entity is ranked among peer entity
instances. Low numbers take priority over higher numbers.
The following code from Liferay's Wiki application's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/wiki/wiki-service/src/main/java/com/liferay/wiki/service/impl/WikiPageLocalServiceImpl.java}{\texttt{WikiPageLocalServiceImpl}}
Java class demonstrates invoking the \texttt{updateEntry} method on the
wiki page entity called \texttt{WikiPage}. In your \texttt{add-} method,
you could invoke \texttt{updateEntry} after adding your entity's
resources. Likewise, in your \texttt{update-} method, you could invoke
\texttt{updateEntry} after calling the \texttt{super.update-} method.
The code below is called in the \texttt{WikiPageLocalServiceImpl}
class's \texttt{updateStatus(...)} method.
\begin{verbatim}
AssetEntry assetEntry = assetEntryLocalService.updateEntry(
userId, page.getGroupId(), page.getCreateDate(),
page.getModifiedDate(), WikiPage.class.getName(),
page.getResourcePrimKey(), page.getUuid(), 0,
assetCategoryIds, assetTagNames, true, true, null, null,
page.getCreateDate(), null, ContentTypes.TEXT_HTML,
page.getTitle(), null, null, null, null, 0, 0, null);
Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
WikiPage.class);
indexer.reindex(page);
\end{verbatim}
Immediately after invoking the \texttt{updateEntry} method, you must
update the respective asset and index the entity instance. The above
code calls the indexer to index (or re-index, if updating) the entity.
That's all there is to it.
\noindent\hrulefill
\textbf{Tip:} The current user's ID and the scope group ID are commonly
made available in service context parameters. If the service context you
use contains them, then you can access them in calls like these:
long userId = serviceContext.getUserId(); long groupId =
serviceContext.getScopeGroupId();
\noindent\hrulefill
Next, you'll learn what's needed to delete an entity that's associated
with an asset.
\section{Deleting Assets}\label{deleting-assets}
When deleting your entities, you should delete the associated assets and
indexes at the same time. This cleans up stored asset and index
information, which keeps the Asset Publisher from showing information
for the entities you've deleted.
In your \texttt{-LocalServiceImpl} Java class, open your
\texttt{delete-} method. After the code that deletes the entity's
resource, delete the entity instance's asset entry and index.
Here's some code which deletes an asset entry and an index associated
with a portlet's entity.
\begin{verbatim}
assetEntryLocalService.deleteEntry(
ENTITY.class.getName(), assetEntry.getEntityId());
Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(ENTITY.class);
indexer.delete(assetEntry);
\end{verbatim}
In your \texttt{-LocalServiceImpl} class, you can write similar code.
Replace the \emph{ENTITY} class name and variable with your entity's
name.
\noindent\hrulefill
\textbf{Important:} For Liferay's Asset Publisher application to show
your entity, the entity must have an Asset Renderer.
Note also that an Asset Renderer is how you show a user the components
of your entity in the Asset Publisher. On deploying your portlet with
asset, indexer, and asset rendering implementations in place, an Asset
Publisher can show your custom entities!
\noindent\hrulefill
\begin{figure}
\centering
\includegraphics{./images/basic-asset-in-asset-publisher.png}
\caption{It can be useful to show custom entities, like this wiki page
entity, in a JSP or in an Asset Publisher.}
\end{figure}
Great! Now you know how to add, update, and delete assets in your apps!
\chapter{Creating an Asset Renderer}\label{creating-an-asset-renderer}
In this tutorial, you'll learn how to create an \texttt{Asset\ Renderer}
and associate your JSP templates with it, along with configuring several
other options by studying a Liferay asset: Blogs.
The Blogs application offers many different ways to access and render a
blogs asset. You'll learn how a blogs asset provides an edit feature,
comment section, original context viewing (i.e., viewing an asset from
the Blogs application), workflow, and more. You'll also learn how it
uses JSP templates to display various blog views. The Blogs application
is an extensive example of how an asset renderer can be customized to
fit your needs.
To learn how an asset renderer is created, you'll create the
pre-existing
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/blogs/web/asset/BlogsEntryAssetRenderer.html}{\texttt{BlogsEntryAssetRenderer}}
class, which configures the asset renderer framework for the Blogs
application.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a new package in your existing project for your asset-related
classes. For instance, the \texttt{BlogsEntryAssetRenderer} class
resides in the \texttt{com.liferay.blogs.web} module's
\texttt{com.liferay.blogs.web.asset} package.
\item
Create your \texttt{-AssetEntry} class for your application in the new
\texttt{-.asset} package and have it implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/asset/kernel/model/AssetEntry.html}{\texttt{AssetEntry}}
interface. Consider the \texttt{BlogsEntryAssetRenderer} class as an
example:
\begin{verbatim}
public class BlogsEntryAssetRenderer
extends BaseJSPAssetRenderer implements TrashRenderer {
\end{verbatim}
The \texttt{BlogsEntryAssetRenderer} class extends the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/asset/kernel/model/BaseJSPAssetRenderer.html}{\texttt{BaseJSPAssetRenderer}},
which is an extension class intended for those who plan on using JSP
templates to generate their asset's HTML. The
\texttt{BaseJSPAssetRenderer} class implements the
\texttt{AssetRenderer} interface. You'll notice the asset renderer
also implements the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/trash/TrashRenderer.html}{\texttt{TrashRenderer}}
interface. This is a common practice for many applications, so they
can use Liferay DXP's Recycle Bin.
\item
Define the asset renderer class's constructor, which typically sets
the asset object to use in the asset renderer class.
\begin{verbatim}
public BlogsEntryAssetRenderer(
BlogsEntry entry, ResourceBundleLoader resourceBundleLoader) {
_entry = entry;
_resourceBundleLoader = resourceBundleLoader;
}
\end{verbatim}
The \texttt{BlogsEntryAssetRenderer} also sets the resource bundle
loader, which loads the language keys for a module. You can learn more
about the resource bundle loader in the
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{Overriding
Language Keys} tutorial.
Also, make sure to define the \texttt{\_entry} and
\texttt{\_resourceBundleLoader} fields in the class:
\begin{verbatim}
private final BlogsEntry _entry;
private final ResourceBundleLoader _resourceBundleLoader;
\end{verbatim}
\item
Now that your class declaration and constructor are defined for the
blogs asset renderer, you must begin connecting your asset renderer to
your asset. The following getter methods accomplish this:
\begin{verbatim}
@Override
public BlogsEntry getAssetObject() {
return _entry;
}
@Override
public String getClassName() {
return BlogsEntry.class.getName();
}
@Override
public long getClassPK() {
return _entry.getEntryId();
}
@Override
public long getGroupId() {
return _entry.getGroupId();
}
@Override
public String getType() {
return BlogsEntryAssetRendererFactory.TYPE;
}
@Override
public String getUuid() {
return _entry.getUuid();
}
\end{verbatim}
The \texttt{getAssetObject()} method sets the \texttt{BlogsEntry} that
was set in the constructor as your asset to track. Likewise, the
\texttt{getType()} method references the blogs asset renderer factory
for the type of asset your asset renderer renders. Of course, the
asset renderer type is \texttt{blog}, which you'll set in the factory
later.
\item
Your asset renderer must link to the portlet that owns the entity. In
the case of a blogs asset, its portlet ID should be linked to the
Blogs application.
\begin{verbatim}
@Override
public String getPortletId() {
AssetRendererFactory assetRendererFactory =
getAssetRendererFactory();
return assetRendererFactory.getPortletId();
}
\end{verbatim}
The \texttt{getPortletId()} method instantiates an asset renderer
factory for a \texttt{BlogsEntry} and retrieves the portlet ID for the
portlet used to display blogs entries.
\item
If you want to enable workflow for your asset, add the following
method similar to what was done for the Blogs application:
\begin{verbatim}
@Override
public int getStatus() {
return _entry.getStatus();
}
\end{verbatim}
This method retrieves the workflow status for the asset.
\item
Another feature many developers want for their asset is comments. This
is enabled for the Blogs application with the following method:
\begin{verbatim}
@Override
public String getDiscussionPath() {
if (PropsValues.BLOGS_ENTRY_COMMENTS_ENABLED) {
return "edit_entry_discussion";
}
else {
return null;
}
}
\end{verbatim}
A comments section is an available option if it returns a non-null
value. For the comments section to display for your asset, you must
enable it in the Asset Publisher's \emph{Options}
(\includegraphics{./images/icon-options.png}) → \emph{Configuration} →
\emph{Setup} → \emph{Display Settings} section.
\item
At a minimum, you should create a title and summary for your asset.
Here's how the \texttt{BlogsEntryAssetRenderer} does it:
\begin{verbatim}
@Override
public String getSummary(
PortletRequest portletRequest, PortletResponse portletResponse) {
int abstractLength = AssetUtil.ASSET_ENTRY_ABSTRACT_LENGTH;
if (portletRequest != null) {
abstractLength = GetterUtil.getInteger(
portletRequest.getAttribute(
WebKeys.ASSET_ENTRY_ABSTRACT_LENGTH),
AssetUtil.ASSET_ENTRY_ABSTRACT_LENGTH);
}
String summary = _entry.getDescription();
if (Validator.isNull(summary)) {
summary = HtmlUtil.stripHtml(
StringUtil.shorten(_entry.getContent(), abstractLength));
}
return summary;
}
@Override
public String getTitle(Locale locale) {
ResourceBundle resourceBundle =
_resourceBundleLoader.loadResourceBundle(
LanguageUtil.getLanguageId(locale));
return BlogsEntryUtil.getDisplayTitle(resourceBundle, _entry);
}
\end{verbatim}
These two methods return information about your asset, so the asset
publisher can display it. The title and summary can be anything.
The \texttt{getSummary(...)} method for Blogs returns the abstract
description for a blog asset. If the abstract description does not
exist, the content of the blog is used as an abstract. You'll learn
more about abstracts and other content specifications later.
The \texttt{getTitle(...)} method for Blogs uses the resource bundle
loader you configured in the constructor to load your module's
resource bundle and return the display title for your asset.
\item
If you want to provide a unique URL for your asset, you can specify a
URL title. A URL title is the URL used to access your asset directly
(e.g., localhost:8080/-/this-is-my-blog-asset). You can do this by
providing the following method:
\begin{verbatim}
@Override
public String getUrlTitle() {
return _entry.getUrlTitle();
}
\end{verbatim}
\item
Insert the \texttt{isPrintable()} method, which enables the Asset
Publisher's printing capability for your asset.
\begin{verbatim}
@Override
public boolean isPrintable() {
return true;
}
\end{verbatim}
This displays a Print icon when your asset is displayed in the Asset
Publisher. For the icon to appear, you must enable it in the Asset
Publisher's \emph{Options} → \emph{Configuration} → \emph{Setup} →
\emph{Display Settings} section.
\begin{figure}
\centering
\includegraphics{./images/asset-publisher-printing.png}
\caption{Enable printing in the Asset Publisher to display the Print
icon for your asset.}
\end{figure}
\item
If your asset is protected by permissions, you can set permissions for
the asset via the asset renderer. See the logic below for an example
used in the \texttt{BlogsEntryAssetRenderer} class:
\begin{verbatim}
@Override
public long getUserId() {
return _entry.getUserId();
}
@Override
public String getUserName() {
return _entry.getUserName();
}
public boolean hasDeletePermission(PermissionChecker permissionChecker) {
return BlogsEntryPermission.contains(
permissionChecker, _entry, ActionKeys.DELETE);
}
@Override
public boolean hasEditPermission(PermissionChecker permissionChecker) {
return BlogsEntryPermission.contains(
permissionChecker, _entry, ActionKeys.UPDATE);
}
@Override
public boolean hasViewPermission(PermissionChecker permissionChecker) {
return BlogsEntryPermission.contains(
permissionChecker, _entry, ActionKeys.VIEW);
}
\end{verbatim}
Before you can check if a user has permission to view your asset, you
must use the \texttt{getUserId()} and \texttt{getUserName()} to
retrieve the entry's user ID and username, respectively. Then there
are three boolean permission methods that check if the user can view,
edit, or delete your blogs entry. These permissions are for specific
entity instances. Global permissions for blog entries are implemented
in the factory, which you'll do later.
\end{enumerate}
Awesome! You've learned how to set up the blogs asset renderer to
\begin{itemize}
\tightlist
\item
connect to an asset
\item
connect to the asset's portlet
\item
use workflow management
\item
use a comments section
\item
retrieve the asset's title and summary
\item
generate the asset's unique URL
\item
display a print icon
\item
check permissions for the asset
\end{itemize}
Now you need to create the templates to render the HTML. The
\texttt{BlogsEntryAssetRenderer} is configured to use JSP templates to
generate HTML for the Asset Publisher. You'll learn more about how to do
this next.
\chapter{Configuring JSP Templates for an Asset
Renderer}\label{configuring-jsp-templates-for-an-asset-renderer}
An asset can be displayed in several different ways in the Asset
Publisher. There are three templates to implement provided by the
\texttt{AssetRenderer} interface:
\begin{itemize}
\tightlist
\item
\texttt{abstract}
\item
\texttt{full\_content}
\item
\texttt{preview}
\end{itemize}
Besides these supported templates, you can also create JSPs for buttons
for direct access and manipulation of the asset. For example,
\begin{itemize}
\tightlist
\item
Edit
\item
View
\item
View in Context
\end{itemize}
The \texttt{BlogsEntryAssetRenderer} customizes the
\texttt{AssetRenderer}'s provided JSP templates and adds a few other
features using JSPs. You'll inspect how the blogs asset renderer is put
together to satisfy JSP template development requirements.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{getJspPath(...)} method to your asset renderer. This
method should return the path to your JSP, which is rendered inside
the Asset Publisher. This is how the \texttt{BlogsEntryAssetRenderer}
uses this method:
\begin{verbatim}
@Override
public String getJspPath(HttpServletRequest request, String template) {
if (template.equals(TEMPLATE_ABSTRACT) ||
template.equals(TEMPLATE_FULL_CONTENT)) {
return "/blogs/asset/" + template + ".jsp";
}
else {
return null;
}
}
\end{verbatim}
Blogs assets provide \texttt{abstract.jsp} and
\texttt{full\_content.jsp} templates. This means that a blogs asset
can render a blog's abstract description or the blog's full content in
the Asset Publisher. Those templates are located in the
\texttt{com.liferay.blogs.web} module's
\texttt{src/main/resources/META-INF/resources/blogs/asset} folder. You
could create a similar folder for your JSP templates used for this
method. The other template provided by the \texttt{AssetRenderer}
interface, \texttt{preview.jsp}, is not customized by the blogs asset
renderer, so its default template is implemented.
You must create a link to display the full content of the asset.
You'll do this later.
\item
Now that you've added the path to your JSP, you must include that JSP.
Since the \texttt{BlogsEntryAssetRenderer} class extends the
\texttt{BaseJSPAssetRenderer}, it already has an \texttt{include(...)}
method to render a specific JSP. You must override this method to set
an attribute in the request to use in the blog's views:
\begin{verbatim}
@Override
public boolean include(
HttpServletRequest request, HttpServletResponse response,
String template)
throws Exception {
request.setAttribute(WebKeys.BLOGS_ENTRY, _entry);
return super.include(request, response, template);
}
\end{verbatim}
The attribute includes the blogs entry object. Adding the blog object
this way is not mandatory; you could obtain the blog entry directly
from the view. Using the \texttt{include(...)} method, however,
follows the best practice for MVC portlets.
\begin{figure}
\centering
\includegraphics{./images/blogs-asset-views.png}
\caption{The abstract and full content views are rendered differently
for blogs.}
\end{figure}
\end{enumerate}
Terrific! You've learned how to apply JSPs supported by the Asset
Publisher for your asset. That's not all you can do with JSP templates,
however! The asset renderer framework provides several other methods
that let you render convenient buttons for your asset.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Blogs assets provide an Edit button for editing the asset. Provide
this by adding the following method to the
\texttt{BlogsEntryAssetRenderer} class:
\begin{verbatim}
@Override
public PortletURL getURLEdit(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse)
throws Exception {
Group group = GroupLocalServiceUtil.fetchGroup(_entry.getGroupId());
PortletURL portletURL = PortalUtil.getControlPanelPortletURL(
liferayPortletRequest, group, BlogsPortletKeys.BLOGS, 0, 0,
PortletRequest.RENDER_PHASE);
portletURL.setParameter("mvcRenderCommandName", "/blogs/edit_entry");
portletURL.setParameter("entryId", String.valueOf(_entry.getEntryId()));
return portletURL;
}
\end{verbatim}
The Asset Publisher loads the blogs asset using the Blogs application.
Then the \texttt{edit\_entry.jsp} template generates the HTML for an
editing UI. Once the necessary edits are made to the asset, it can be
saved from the Asset Publisher. Pretty cool, right?
\item
You can specify how to view your asset by providing methods similar to
the methods outlined below in the \texttt{BlogsEntryAssetRenderer}
class:
\begin{verbatim}
@Override
public String getURLView(
LiferayPortletResponse liferayPortletResponse,
WindowState windowState)
throws Exception {
AssetRendererFactory assetRendererFactory =
getAssetRendererFactory();
PortletURL portletURL = assetRendererFactory.getURLView(
liferayPortletResponse, windowState);
portletURL.setParameter("mvcRenderCommandName", "/blogs/view_entry");
portletURL.setParameter("entryId", String.valueOf(_entry.getEntryId()));
portletURL.setWindowState(windowState);
return portletURL.toString();
}
@Override
public String getURLViewInContext(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse,
String noSuchEntryRedirect) {
return getURLViewInContext(
liferayPortletRequest, noSuchEntryRedirect, "/blogs/find_entry",
"entryId", _entry.getEntryId());
}
\end{verbatim}
The \texttt{getURLView(...)} method generates a URL that displays the
full content of the asset in the Asset Publisher. This is assigned to
the clickable asset name. The \texttt{getURLViewInContext(...)} method
provides a similar URL assigned to the asset name, but the URL
redirects to the original context of the asset (e.g., viewing a blogs
asset in the Blogs application). Deciding which view to render is
configurable by navigating to the Asset Publisher's \emph{Options} →
\emph{Configuration} → \emph{Setup} → \emph{Display Settings} section
and choosing between \emph{Show Full Content} and \emph{View in
Context} for the Asset Link Behavior drop-down menu.
\end{enumerate}
The Blogs application provides \texttt{abstract} and
\texttt{full\_content} JSP templates that override the ones provided by
the \texttt{AssetRenderer} interface. The third template,
\texttt{preview}, could also be customized. You can view the default
\texttt{preview.jsp} template rendered in the \emph{Add} →
\emph{Content} menu.
\begin{figure}
\centering
\includegraphics{./images/preview-template-asset-renderer.png}
\caption{The \texttt{preview} template displays a preview of the asset
in the Content section of the Add menu.}
\end{figure}
You've learned all about implementing the \texttt{AssetRenderer}'s
provided templates and customizing them to fit your needs. Next, you'll
put your asset renderer into action by creating a factory.
\chapter{Creating a Factory for the Asset
Renderer}\label{creating-a-factory-for-the-asset-renderer}
You've successfully created an asset renderer, but you must create a
factory class to generate asset renderers for each asset instance. For
example, the blogs asset renderer factory instantiates
\texttt{BlogsEntryAssetRenderer} for each blogs asset displayed in an
Asset Publisher.
You'll continue the blogs asset renderer example by creating the blogs
asset renderer factory.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an \texttt{-AssetRenderFactory} class in the same folder as its
asset renderer class. For blogs, the
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/blogs/web/asset/BlogsEntryAssetRendererFactory.html}{\texttt{BlogsEntryAssetRendererFactory}}
class resides in the \texttt{com.liferay.blogs.web} module's
\texttt{com.liferay.blogs.web.asset} package. The factory class should
extend the \texttt{BaseAssetRendererFactory} class and the asset type
should be specified as its parameter. You can see how this was done in
the \texttt{BlogsEntryAssetRendererFactory} class below
\begin{verbatim}
public class BlogsEntryAssetRendererFactory
extends BaseAssetRendererFactory {
\end{verbatim}
\item
Create an \texttt{@Component} annotation section above the class
declaration. This annotation is responsible for registering the
factory instance for the asset.
\begin{verbatim}
@Component(
immediate = true,
property = {"javax.portlet.name=" + BlogsPortletKeys.BLOGS},
service = AssetRendererFactory.class
)
public class BlogsEntryAssetRendererFactory
extends BaseAssetRendererFactory {
\end{verbatim}
There are a few annotation elements you should set:
\begin{itemize}
\tightlist
\item
The \texttt{immediate} element directs the factory to start in
Liferay DXP when its module starts.
\item
The \texttt{property} element sets the portlet that is associated
with the asset. The Blogs portlet is specified, since this is the
Blogs asset renderer factory.
\item
The \texttt{service} element should point to the
\texttt{AssetRendererFactory.class} interface.
\end{itemize}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** If you're using a Java EE portlet WAR, you must register the asset
renderer factory in the portlet's `liferay-portlet.xml` file. In an
OSGi-based Liferay MVC portlet, the registration
process is completed automatically by OSGi using the `@Component`
annotation.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Create a constructor for the factory class that presets private
attributes of the factory.
\begin{verbatim}
public BlogsEntryAssetRendererFactory() {
setClassName(BlogsEntry.class.getName());
setLinkable(true);
setPortletId(BlogsPortletKeys.BLOGS);
setSearchable(true);
}
\end{verbatim}
\texttt{linkable}: other assets can select blogs assets as their
related assets.
\texttt{searchable}: blogs can be found when searching for assets.
Setting the class name and portlet ID links the asset renderer factory
to the entity.
\item
Create the asset renderer for your asset. This is done by calling its
constructor.
\begin{verbatim}
@Override
public AssetRenderer getAssetRenderer(long classPK, int type)
throws PortalException {
BlogsEntry entry = _blogsEntryLocalService.getEntry(classPK);
BlogsEntryAssetRenderer blogsEntryAssetRenderer =
new BlogsEntryAssetRenderer(entry, _resourceBundleLoader);
blogsEntryAssetRenderer.setAssetRendererType(type);
blogsEntryAssetRenderer.setServletContext(_servletContext);
return blogsEntryAssetRenderer;
}
\end{verbatim}
For blogs, the asset is retrieved by calling the Blogs application's
local service. Then the asset renderer is instantiated using the blogs
asset and resource bundle loader. Next, the type and servlet context
is set for the asset renderer. Finally, the configured asset renderer
is returned.
There are a few variables in the \texttt{getAssetRenderer(...)} method
you must create. You'll set those variables and learn what they're
doing next.
\begin{enumerate}
\def\labelenumii{\alph{enumii}.}
\tightlist
\item
You must get the entry by calling the Blogs application's local
service. You can instantiate this service by creating a private
field and setting it using a setter method:
\end{enumerate}
\begin{verbatim}
@Reference(unbind = "-")
protected void setBlogsEntryLocalService(
BlogsEntryLocalService blogsEntryLocalService) {
_blogsEntryLocalService = blogsEntryLocalService;
}
private BlogsEntryLocalService _blogsEntryLocalService;
\end{verbatim}
The setter method is annotated with the \texttt{@Reference} tag.
\begin{enumerate}
\def\labelenumii{\alph{enumii}.}
\setcounter{enumii}{1}
\tightlist
\item
You must specify the resource bundle loader since it was specified
in the \texttt{BlogsEntryAssetRenderer}'s constructor:
\end{enumerate}
\begin{verbatim}
@Reference(
target = "(bundle.symbolic.name=com.liferay.blogs.web)", unbind = "-"
)
public void setResourceBundleLoader(
ResourceBundleLoader resourceBundleLoader) {
_resourceBundleLoader = resourceBundleLoader;
}
private ResourceBundleLoader _resourceBundleLoader;
\end{verbatim}
Make sure the \texttt{osgi.web.symbolicname} in the \texttt{target}
property of the \texttt{@Reference} annotation is set to the same
value as the \texttt{Bundle-SymbolicName} defined in the
\texttt{bnd.bnd} file of the module the factory resides in.
\begin{enumerate}
\def\labelenumii{\alph{enumii}.}
\setcounter{enumii}{2}
\item
The asset renderer \texttt{type} integer is set for the asset
renderer, but why an integer? Liferay DXP needs to differentiate
when it should display the latest \emph{approved} version of the
asset, or the latest version, even if it's unapproved (e.g.,
unapproved versions would be displayed for reviewers of the asset in
a workflow). For these situations, the asset renderer factory should
receive either
\begin{itemize}
\tightlist
\item
\texttt{0} for the latest version of the asset
\item
\texttt{1} for the latest approved version of the asset
\end{itemize}
\item
Since the Blogs application provides its own JSPs, it must pass a
reference of the servlet context to the asset renderer. This is
always required when using custom JSPs in an asset renderer:
\end{enumerate}
\begin{verbatim}
@Reference(
target = "(osgi.web.symbolicname=com.liferay.blogs.web)", unbind = "-"
)
public void setServletContext(ServletContext servletContext) {
_servletContext = servletContext;
}
private ServletContext _servletContext;
\end{verbatim}
\item
Set the type of asset that the asset factory associates with and
provide a getter method to retrieve that type. Also, provide another
getter to retrieve the blogs entry class name, which is required:
\begin{verbatim}
public static final String TYPE = "blog";
@Override
public String getType() {
return TYPE;
}
@Override
public String getClassName() {
return BlogsEntry.class.getName();
}
\end{verbatim}
\item
Set the Lexicon icon for the asset:
\begin{verbatim}
@Override
public String getIconCssClass() {
return "blogs";
}
\end{verbatim}
You can find a list of all available Lexicon icons
\href{https://liferay.design/lexicon/core-components/icons/}{here}.
\item
Add methods that generate URLs to add and view the asset.
\begin{verbatim}
@Override
public PortletURL getURLAdd(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse, long classTypeId) {
PortletURL portletURL = PortalUtil.getControlPanelPortletURL(
liferayPortletRequest, getGroup(liferayPortletRequest),
BlogsPortletKeys.BLOGS, 0, 0, PortletRequest.RENDER_PHASE);
portletURL.setParameter("mvcRenderCommandName", "/blogs/edit_entry");
return portletURL;
}
@Override
public PortletURL getURLView(
LiferayPortletResponse liferayPortletResponse,
WindowState windowState) {
LiferayPortletURL liferayPortletURL =
liferayPortletResponse.createLiferayPortletURL(
BlogsPortletKeys.BLOGS, PortletRequest.RENDER_PHASE);
try {
liferayPortletURL.setWindowState(windowState);
}
catch (WindowStateException wse) {
}
return liferayPortletURL;
}
\end{verbatim}
If you're paying close attention, you may have noticed the
\texttt{getURLView(...)} method was also implemented in the
\texttt{BlogsEntryAssetRenderer} class. The asset renderer's
\texttt{getURLView(...)} method creates a URL for the specific asset
instance, whereas the factory uses the method to create a generic URL
that only points to the application managing the assets (e.g., Blogs
application).
\item
Set the global permissions for all blogs assets:
\begin{verbatim}
@Override
public boolean hasAddPermission(
PermissionChecker permissionChecker, long groupId, long classTypeId)
throws Exception {
return BlogsPermission.contains(
permissionChecker, groupId, ActionKeys.ADD_ENTRY);
}
@Override
public boolean hasPermission(
PermissionChecker permissionChecker, long classPK, String actionId)
throws Exception {
return BlogsEntryPermission.contains(
permissionChecker, classPK, actionId);
}
\end{verbatim}
\end{enumerate}
Great! You've finished creating the Blogs application's asset renderer
factory! Now you have the knowledge to implement an asset renderer and
produce an asset renderer for each asset instance using a factory!
\chapter{Implementing Asset Categorization and
Tagging}\label{implementing-asset-categorization-and-tagging}
Now it's time to get started with Tags and Categories.
\section{Adding Tags and Categories}\label{adding-tags-and-categories}
You can use the following tags in the JSPs you provide for
adding/editing custom entities. Here's what the tags look like in the
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/blogs/blogs-web/src/main/resources/META-INF/resources/blogs/edit_entry.jsp}{edit\_entry.jsp}
for the Blogs portlet:
\begin{verbatim}
...
...
...
\end{verbatim}
The \texttt{liferay-asset:asset-categories-selector} and
\texttt{liferay-asset:asset-tags-selector} tags generate form controls
that let users browse/select categories for the entity, browse/select
tags, and/or create new tags to associate with the entity.
The \texttt{liferay-ui:asset-categories-error} and
\texttt{liferay-ui:asset-tags-error} tags show messages for errors
occurring during the asset category or tag input process. The
\texttt{aui:fieldset} tag uses a container that lets users hide or show
the category and tag input options.
For styling purposes, the \texttt{aui:fieldset-group} tag is given the
\texttt{lexicon} markup view.
\section{Displaying Tags and
Categories}\label{displaying-tags-and-categories}
Tags and categories should be displayed with the content of the asset.
Here's how to display the tags and categories:
\begin{verbatim}
...
\end{verbatim}
The \texttt{portletURL} parameter is used for both tags and categories.
Each tag that uses this parameter becomes a link containing the
\texttt{portletURL} \emph{and} \texttt{tag} or \texttt{categoryId}
parameter value. To implement this, you must implement the look-up
functionality in your portlet code. Do this by reading the values of
those two parameters and using \texttt{AssetEntryService} to query the
database for entries based on the specified tag or category.
Deploy your changes and add/edit a custom entity in your UI. Your form
shows the categorization and tag input options in a panel that the user
can hide/show.
Great! Now you know how to make category and tag input options available
to your app's content authors.
\chapter{Relating Assets}\label{relating-assets-1}
After you complete
\href{/docs/frameworks/7-2/-/knowledge_base/frameworks/adding-updating-and-deleting-assets}{Adding,
Updating, and Deleting Assets} for your application you can go ahead and
begin relating your assets!
\section{Relating Assets in the Service
Layer}\label{relating-assets-in-the-service-layer}
First, you must make some modifications to your portlet's service layer.
You must implement persisting your entity's asset relationships.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your portlet's \texttt{service.xml}, put the following line of code
below any finder method elements and then run Service Builder:
\begin{verbatim}
\end{verbatim}
\item
Modify the \texttt{add-}, \texttt{delete-}, and \texttt{update-}
methods in your \texttt{-LocalServiceImpl} to persist the asset
relationships. You'll use your \texttt{-LocalServiceImpl}'s
\texttt{assetLinkLocalService} instance variable to execute
persistence actions.
For example, consider the Wiki application. When you update wiki
assets and statuses, both methods utilize the \texttt{updateLinks} via
your instance variable \texttt{assetLinkLocalService}. Here's the
\texttt{updateLinks} invocation in the Wiki application's
\texttt{WikiPageLocalServiceImpl.updateStatus(...)} method:
\begin{verbatim}
assetLinkLocalService.updateLinks(
userId, assetEntry.getEntryId(), assetLinkEntryIds,
AssetLinkConstants.TYPE_RELATED);
\end{verbatim}
To call the \texttt{updateLinks} method, you must pass in the current
user's ID, the asset entry's ID, the asset link entries' IDs, and the
link type. Invoke this method after creating the asset entry. If you
assign to an \texttt{AssetEntry} variable (e.g., one called
\texttt{assetEntry}) the value returned from invoking
\texttt{assetEntryLocalService.updateEntry}, you can get the asset
entry's ID for updating its asset links. Lastly, in order to specify
the link type parameter, make sure to import
\texttt{com.liferay.portlet.asset.model.AssetLinkConstants}.
\item
In your \texttt{-LocalServiceImpl} class' \texttt{delete-} method, you
must delete the asset's relationships before deleting the asset. For
example, you could delete your existing asset link relationships by
using the following code:
\begin{verbatim}
AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
ENTITY.class.getName(), ENTITYId);
assetLinkLocalService.deleteLinks(assetEntry.getEntryId());
\end{verbatim}
\end{enumerate}
Make sure to replace the \emph{ENTITY} place holders for your custom
\texttt{-delete} method.
Super! Now your portlet's service layer can handle related assets. Even
so, there's still nothing in your portlet's UI that lets your users
relate assets. You'll take care of that in the next step.
\section{Relating Assets in the UI}\label{relating-assets-in-the-ui}
The UI for linking assets should be in the JSP where users create and
edit your entity. This way only content creators can relate other assets
to the entity. Related assets are implemented in the JSP by using the
Liferay UI tag \texttt{liferay-ui:input-asset-links} inside a
collapsible panel. This code is placed inside the \texttt{aui:fieldset}
tags of the JSP.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{liferay-asset:input-asset-links} tag to your form.
Here's how it's added in the Blogs application:
\begin{verbatim}
\end{verbatim}
The following screenshot shows the Related Assets menu for an
application. Note that it is contained in a collapsible panel titled
Related Assets.
\begin{figure}
\centering
\includegraphics{./images/related-assets-select-menu.png}
\caption{Your portlet's entity is now available in the Related Assets
\emph{Select} menu.}
\end{figure}
\item
Unfortunately, the Related Assets menu shows your entity's fully
qualified class name. To replace it with a simplified name for your
entity, add a language key with the fully qualified class name for the
key and the name you want for the value. Put the language key in file
\texttt{docroot/WEB-INF/src/content/Language.properties} in your
portlet. You can refer to the
\href{/docs/frameworks/7-2/-/knowledge_base/frameworks/overriding-language-keys}{Overriding
Language Keys} tutorial for more documentation on using language
properties.
Upon redeploying your portlet, the value you assigned to the fully
qualified class name in your \texttt{Language.properties} file shows
in the Related Assets menu.
\end{enumerate}
Awesome! Now content creators and editors can relate the assets of your
application. The next thing you need to do is reveal any such related
assets to the rest of your application's users. After all, you don't
want to give everyone edit access just so they can view related assets!
\section{Showing Related Assets}\label{showing-related-assets}
You can show related assets in your application's view of that entity
or, if you've implemented asset rendering for your custom entity, you
can show related assets in the full content view of your entity for
users to view in an Asset Publisher portlet.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
You must get the \texttt{AssetEntry} object associated with your
entity:
\begin{verbatim}
<%
long insultId = ParamUtil.getLong(renderRequest, "insultId");
Insult ins = InsultLocalServiceUtil.getInsult(insultId);
AssetEntry assetEntry = AssetEntryLocalServiceUtil.getEntry(Insult.class.getName(), ins.getInsultId());
%>
\end{verbatim}
\item
Use the \texttt{liferay-asset:asset-links} tag to show the entity's
related assets. For this tag, you retrieve the \texttt{assetEntryId}
from the \texttt{assetEntry} object, retrieve your asset's
\texttt{className}, and get the entity's primary key
(\texttt{classPK}) from the specific \texttt{entry}. The tag then
retrieves any other assets linked to your asset.
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Great! Now you have the JSP that lets your users view related assets.
Related assets, if you've created any yet, should be visible near the
bottom of the page.
Excellent! Now you know how to implement related assets in your apps.
\chapter{Implementing Asset
Priority}\label{implementing-asset-priority-1}
This asset priority field isn't enabled when you create an asset. You
must manually add support for it. You'll learn how below.
\section{Add the Priority Field to Your
JSP}\label{add-the-priority-field-to-your-jsp}
In the JSP for adding and editing your asset, add the following input
field that lets users set the asset's priority. This example also
validates the input to make sure the value the user sets is a number
higher than zero:
\begin{verbatim}
[0]
\end{verbatim}
That's it for the view layer! Now when users create or edit your asset,
they can enter its priority. Next, you'll learn how to use that value in
your service layer.
\section{Using the Priority Value in Your Service
Layer}\label{using-the-priority-value-in-your-service-layer}
To make the priority value functional, you must retrieve it from the
view and add it to the asset in your database. The priority value is
automatically available in your service layer via the
\texttt{ServiceContext} variable \texttt{serviceContext}. Retrieve it
with \texttt{serviceContext.getAssetPriority()}, and then pass it as the
last argument to the \texttt{assetEntryLocalService.updateEntry} call in
your \texttt{-LocalServiceImpl}. You can see an example of this in
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/service/impl/BlogsEntryLocalServiceImpl.java}{the
\texttt{BlogsEntryLocalServiceImpl} class} of Liferay DXP's Blogs app.
The \texttt{updateAsset} method takes a \texttt{priority} argument,
which it passes as the last argument to its
\texttt{assetEntryLocalService.updateEntry} call:
\begin{verbatim}
@Override
public void updateAsset(
long userId, BlogsEntry entry, long[] assetCategoryIds,
String[] assetTagNames, long[] assetLinkEntryIds, Double priority)
throws PortalException {
...
AssetEntry assetEntry = assetEntryLocalService.updateEntry(
userId, entry.getGroupId(), entry.getCreateDate(),
entry.getModifiedDate(), BlogsEntry.class.getName(),
entry.getEntryId(), entry.getUuid(), 0, assetCategoryIds,
assetTagNames, true, visible, null, null, null, null,
ContentTypes.TEXT_HTML, entry.getTitle(), entry.getDescription(),
summary, null, null, 0, 0, priority);
...
}
\end{verbatim}
The \texttt{BlogsEntryLocalServiceImpl} class calls this
\texttt{updateAsset} method when adding or updating a blog entry. Note
that \texttt{serviceContext.getAssetPriority()} retrieves the priority:
\begin{verbatim}
updateAsset(
userId, entry, serviceContext.getAssetCategoryIds(),
serviceContext.getAssetTagNames(),
serviceContext.getAssetLinkEntryIds(),
serviceContext.getAssetPriority());
\end{verbatim}
Sweet! Now you know how to enable priorities for your app's assets.
\chapter{Back-end Frameworks}\label{back-end-frameworks}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay's powerful back-end frameworks provide essential services behind
the scenes. Here are some of the frameworks:
\begin{itemize}
\tightlist
\item
\hyperref[portlet-providers]{Portlet Providers}
\item
\hyperref[data-scopes]{Data Scopes}
\item
\hyperref[message-bus]{Message Bus}
\end{itemize}
You can use these frameworks to provide important functionality to your
applications.
\section{Portlet Providers}\label{portlet-providers}
Some apps perform the same operations on different entity types. For
example, the Asset Publisher lets users browse, add, preview, and view
various entities as assets including documents, web content, blogs, and
more. The entities vary, but the operations and surrounding business
logic stay the same. Apps like the Asset Publisher rely on the Portlet
Providers framework to fetch portlets to operate on the entities. In
this way, the framework lets you focus on entity operations and frees
you from concern about portlets that carry out those operations.
\section{Portlet Provider Classes}\label{portlet-provider-classes}
Portlet Provider classes are components that implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProvider.html}{\texttt{PortletProvider}}
interface, and are associated with an entity type. Once you've
registered a Portlet Provider, you can invoke the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html}{\texttt{PortletProviderUtil}}
class to retrieve the portlet ID or portlet URL from that Portlet
Provider.
As an example, examine the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/wiki/wiki-web/src/main/java/com/liferay/wiki/web/internal/portlet/WikiEditPortletProvider.java}{\texttt{WikiEditPortletProvider}}
class:
\begin{verbatim}
@Component(
immediate = true,
property = {
"model.class.name=com.liferay.wiki.model.WikiPage",
"service.ranking:Integer=100"
},
service = EditPortletProvider.class
)
public class WikiEditPortletProvider
extends BasePortletProvider implements EditPortletProvider {
@Override
public String getPortletName() {
return WikiPortletKeys.WIKI;
}
}
\end{verbatim}
\texttt{WikiEditPortletProvider} extends
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BasePortletProvider.html}{\texttt{BasePortletProvider}},
inheriting its \texttt{getPortletURL} methods.
\texttt{WikiEditPortletProvider} must, however, implement
\texttt{PortletProvider}'s \texttt{getPortletName} method, which returns
the portlet's name \texttt{WikiPortletKeys.WIKI}.
\noindent\hrulefill
\textbf{Note:} If you're creating a Portlet Provider for one of
Liferay's portlets, your \texttt{getPortletName} method should return
the portlet name from that portlet's \texttt{*PortletKeys} class, if
such a class exists.
\noindent\hrulefill
The \texttt{@Component} annotation for \texttt{WikiEditPortletProvider}
specifies these elements and properties:
\begin{itemize}
\tightlist
\item
\texttt{immediate\ =\ true} activates the component immediately upon
installation.
\item
\texttt{"model.class.name=com.liferay.wiki.model.WikiPage"} specifies
the entity type the portlet operates on.
\item
\texttt{"service.ranking:Integer=100"} sets the component's rank to
\texttt{100}, prioritizing it above all Portlet Providers that specify
the same \texttt{model.class.name} value but have a lower rank.
\item
\texttt{service\ =\ EditPortletProvider.class} reflects the
subinterface \texttt{PortletProvider} class this class implements
(\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/EditPortletProvider.html}{\texttt{EditPortletProvider}}).
\end{itemize}
For step-by-step instructions on creating a Portlet Provider class, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-portlet-providers}{Creating
Portlet Providers}. For instructions on using Portlet Providers to
retrieve a portlet, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/retrieving-portlets}{Retrieving
Portlets}.
\section{Data Scopes}\label{data-scopes}
Apps can restrict their data to specific \emph{scopes}. Scopes provide a
context for the application's data.
\textbf{Global:} One data set throughout a portal instance.
\textbf{Site:} One data set for each Site.
\textbf{Page:} One data set for each Page on a Site.
For example, a Site-scoped app has one set of data on one Site and a
completely different set of data for another Site. For a detailed
explanation of scopes, see the user guide article
\href{/docs/7-2/user/-/knowledge_base/u/widget-scope}{Widget Scope}. To
give your applications scope, you must manually add support for it. For
instructions on this, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/enabling-and-accessing-data-scopes}{Enabling
and Accessing Data Scopes}.
\section{Accessing the Site Scope Across
Apps}\label{accessing-the-site-scope-across-apps}
There may be times when you must access a different app's Site-scoped
data from your app that is scoped to a page or the portal. For example,
web content articles can be created in the page, Site, or portal scope.
\href{/docs/7-2/user/-/knowledge_base/u/designing-uniform-content}{Structures
and Templates} for such articles, however, exist only in the Site scope.
Other techniques return your app's scope, which might not be the Site
scope. What a pickle! Never fear, the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html}{\texttt{ThemeDisplay}}
method \texttt{getSiteGroupId()} is here! This method always gets the
Site scope, no matter your app's current scope. For an example of using
this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/enabling-and-accessing-data-scopes}{Enabling
and Accessing Data Scopes}.
\section{Message Bus}\label{message-bus}
If you must ever do data processing outside the scope of the web's
request/response, look no further than the Message Bus. It's
conceptually similar to Java Messaging Service (JMS) Topics, but
sacrifices transactional, reliable delivery capabilities, making it much
lighter-weight. Liferay DXP uses Message Bus in many places:
\begin{itemize}
\tightlist
\item
Auditing
\item
Search engine integration
\item
Email subscriptions
\item
Monitoring
\item
Document Library processing
\item
Background tasks
\item
Cluster-wide request execution
\item
Clustered cache replication
\end{itemize}
You can use it too! Here are some of Message Bus's most important
features:
\begin{itemize}
\tightlist
\item
publish/subscribe messaging
\item
request queuing and throttling
\item
flow control
\item
multi-thread message processing
\end{itemize}
There are also tools, such as the Java SE's JConsole, that can monitor
Message Bus activities.
\begin{figure}
\centering
\includegraphics{./images/message-bus-jconsole.png}
\caption{JConsole shows statistics on Message Bus messages sent,
messages pending, and more.}
\end{figure}
\chapter{Creating Portlet Providers}\label{creating-portlet-providers}
Follow these steps to create your own
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#portlet-providers}{Portlet
Provider}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
an OSGi module}.
\item
Create a \texttt{PortletProvider} class in your module. Use the
recommended class naming convention:
\texttt{{[}Entity{]}\ +\ {[}Action{]}\ +\ PortletProvider}
For example, here's a Portlet Provider class for viewing a
\texttt{LanguageEntry}:
\texttt{LanguageEntryViewPortletProvider}
\item
Extend
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BasePortletProvider.html}{\texttt{BasePortletProvider}}
if you want to use its \texttt{getPortletURL} method implementations.
\item
Implement one or more
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProvider.html}{\texttt{PortletProvider}}
subinterfaces that match your action(s):
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/AddPortletProvider.html}{\texttt{AddPortletProvider}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BrowsePortletProvider.html}{\texttt{BrowsePortletProvider}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/EditPortletProvider.html}{\texttt{EditPortletProvider}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/ManagePortletProvider.html}{\texttt{ManagePortletProvider}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PreviewPortletProvider.html}{\texttt{PreviewPortletProvider}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/ViewPortletProvider.html}{\texttt{ViewPortletProvider}}
\end{itemize}
\item
Make the class an OSGi Component by adding an annotation like this
one:
\begin{verbatim}
@Component(
immediate = true,
property = {
"model.class.name=CLASS_NAME",
"service.ranking:Integer=10"
},
service = {INTERFACE_1.class, ...}
)
\end{verbatim}
The \texttt{immediate\ =\ true} element specifies that the component
should be activated immediately upon installation.
Assign to the \texttt{model.class.name} property the fully qualified
class name of the entity the portlet operates on. Here's an example
\texttt{model.class.name} property for the \texttt{WikiPage} entity:
\begin{verbatim}
"model.class.name=com.liferay.wiki.model.WikiPage"
\end{verbatim}
Assign the \texttt{service} element to the \texttt{PortletProvider}
subinterface(s) you're implementing (e.g.,
\texttt{ViewPortletProvider.class},
\texttt{BrowsePortletProvider.class}, etc.). This example sets
\texttt{service} to \texttt{EditPortletProvider.class}:
\begin{verbatim}
service = EditPortletProvider.class
\end{verbatim}
\item
If you're overriding an existing Portlet Provider, rank your Portlet
Provider higher by specifying a \texttt{service.ranking:Integer}
property with a higher integer value:
\begin{verbatim}
property = {
...,
"service.ranking:Integer=10"
}
\end{verbatim}
\item
Implement the provider methods you want. Be sure to implement the
\texttt{PortletProvider} method \texttt{getPortletName}. If you didn't
extend \texttt{BasePortletProvider}, implement
\texttt{PortletProvider}'s \texttt{getPortletURL} methods too.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy
your module}.
\end{enumerate}
Now your Portlet Provider is available to return the ID and URL of the
portlet that provides the desired behaviors. For more information on
this, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/retrieving-portlets}{Retrieving
Portlets}.
\section{Related Topics}\label{related-topics-4}
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#portlet-providers}{Portlet
Providers}
\href{/docs/7-2/frameworks/-/knowledge_base/f/retrieving-portlets}{Retrieving
Portlets}
\chapter{Retrieving Portlets}\label{retrieving-portlets}
When a
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#portlet-providers}{Portlet
Provider} exists for an entity, you can use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html}{\texttt{PortletProviderUtil}}
class to retrieve the ID or URL of the portlet that performs the entity
action you want.
The Portlet Provider framework's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProvider.Action.html}{\texttt{PortletProvider.Action}}
Enum defines these action types:
\begin{itemize}
\tightlist
\item
\texttt{ADD}
\item
\texttt{BROWSE}
\item
\texttt{EDIT}
\item
\texttt{MANAGE}
\item
\texttt{PREVIEW}
\item
\texttt{VIEW}
\end{itemize}
The action type and entity type are key parameters in fetching a
portlet's ID or URL.
\section{Fetching a Portlet ID}\label{fetching-a-portlet-id}
To get the ID of the portlet that performs an action on an entity, pass
that entity and action as arguments to the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html}{\texttt{PortletProviderUtil}}
method \texttt{getPortletId}. For example, this call gets the ID of a
portlet for viewing Recycle Bin entries:
\begin{verbatim}
String portletId = PortletProviderUtil.getPortletId(
"com.liferay.portlet.trash.model.TrashEntry",
PortletProvider.Action.VIEW);
\end{verbatim}
The \texttt{com.liferay.portlet.trash.model.TrashEntry} entity specifies
Recycle Bin entries, and \texttt{PortletProvider.Action.VIEW} specifies
the view action.
How and where you use the portlet ID depends on your needs---there's no
typical use case or set of steps to follow. One example is how the Asset
Publisher uses the Portlet Provider framework to add a previewed asset
to a page; it adds the asset to a portlet and adds that portlet to the
page. The Asset Publisher uses the \texttt{liferay-asset:asset\_display}
tag library tag whose \texttt{asset\_display/preview.jsp} shows an
\emph{Add} button for adding the portlet. If the previewed asset is a
Blogs entry, for example, the framework returns a blogs portlet ID or
URL for adding the portlet to the current page. Here's the relevant code
from the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/asset/asset-taglib/src/main/resources/META-INF/resources/asset_display/preview.jsp\#L75-L91}{\texttt{asset\_display/preview.jsp}}:
\begin{verbatim}
<%
Map data = new HashMap();
String portletId = PortletProviderUtil.getPortletId(assetEntry.getClassName(), PortletProvider.Action.ADD);
data.put("portlet-id", portletId);
%>
\end{verbatim}
This code invokes
\texttt{PortletProviderUtil.getPortletId(assetEntry.getClassName(),\ PortletProvider.Action.ADD)}
to get the ID of a portlet that adds and displays the asset of the
underlying entity class.
The JSP puts the portlet ID into the \texttt{data} map:
\begin{verbatim}
data.put("portlet-id", portletId);
\end{verbatim}
Then it passes the \texttt{data} map to a new \emph{Add} button that
adds the portlet to the page:
\begin{verbatim}
\end{verbatim}
\section{Fetching a Portlet URL}\label{fetching-a-portlet-url}
To get the URL of the portlet that performs an action on an entity, call
one of
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html}{\texttt{PortletProviderUtil}'s}
\texttt{getPortletURL} methods. These methods return a
\texttt{javax.portlet.PortletURL} based on an
\texttt{HttpServletRequest} or \texttt{PortletRequest}. You can also
specify a \texttt{Group}, the entity's class name, and the action.
How you call these methods depends on your use case---there's no typical
set of steps to follow. As an example, when the Asset Publisher is
configured in Manual mode, the user can use an Asset Browser to select
asset entries. The \texttt{asset-publisher-web} module's
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/asset/asset-publisher-web/src/main/resources/META-INF/resources/configuration/asset_entries.jsp\#L123}{\texttt{configuration/asset\_entries.jsp}}
file uses \texttt{PortletProviderUtil}'s \texttt{getPortletURL} method
(at the end of the code below) to generate a corresponding Asset Browser
URL:
\begin{verbatim}
List> assetRendererFactories =
ListUtil.sort(
AssetRendererFactoryRegistryUtil.getAssetRendererFactories(
company.getCompanyId()),
new AssetRendererFactoryTypeNameComparator(locale));
for (AssetRendererFactory> curRendererFactory : assetRendererFactories) {
long curGroupId = groupId;
if (!curRendererFactory.isSelectable()) {
continue;
}
PortletURL assetBrowserURL = PortletProviderUtil.getPortletURL(
request, curRendererFactory.getClassName(),
PortletProvider.Action.BROWSE);
\end{verbatim}
\section{Related Topics}\label{related-topics-5}
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#portlet-providers}{Portlet
Providers}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-portlet-providers}{Creating
Portlet Providers}
\chapter{Enabling and Accessing Data
Scopes}\label{enabling-and-accessing-data-scopes}
Apps can restrict their data to specific scopes (e.g., Global, Site,
Page). Here, you'll learn how to
\begin{itemize}
\tightlist
\item
\hyperref[enabling-scoping]{Enable Scoping}
\item
\hyperref[accessing-your-apps-scope]{Access Your App's Scope}
\item
\hyperref[accessing-the-site-scope]{Access the Site Scope}
\end{itemize}
For more detailed information about scoping, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#data-scopes}{Data
Scopes}.
\section{Enabling Scoping}\label{enabling-scoping}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Scope your app's entities. In your service layer, your entities must
have a \texttt{companyId} attribute of type \texttt{long} to enable
scoping by portal instance, and a \texttt{groupId} attribute of type
\texttt{long} to enable scoping by Site. Using
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} is the simplest way to do this. For instructions on this, see
\href{/docs/7-2/appdev/-/knowledge_base/a/creating-a-service-builder-project}{Service
Builder Persistence} and
\href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Business
Logic with Service Builder}.
\item
To enable scoping in your app, set the property
\texttt{"com.liferay.portlet.scopeable=true"} in your portlet class's
\texttt{@Component} annotation. For example, the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/journal/journal-content-web/src/main/java/com/liferay/journal/content/web/internal/portlet/JournalContentPortlet.java}{Web
Content Display Portlet's portlet class} sets this component property:
\begin{verbatim}
@Component(
immediate = true,
property = {
...
"com.liferay.portlet.scopeable=true",
...,
},
service = Portlet.class
)
public class JournalContentPortlet extends MVCPortlet {
...
}
\end{verbatim}
\end{enumerate}
\section{Accessing Your App's Scope}\label{accessing-your-apps-scope}
Users can typically set an app's scope to a page, a Site, or the entire
portal. To handle your app's data, you must access it in its current
scope. Your app's scope is available in these ways:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Via the \texttt{scopeGroupId} variable injected in JSPs that use the
\texttt{\textless{}liferay-theme:defineObjects\ /\textgreater{}} tag.
This variable contains your app's current scope. For example, the
Liferay Bookmarks app's
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/bookmarks/bookmarks-web/src/main/resources/META-INF/resources/bookmarks/view.jsp\#L122-L125}{\texttt{view.jsp}}
uses its \texttt{scopeGroupId} to retrieve the bookmarks and total
number of bookmarks in the current scope:
\begin{verbatim}
...
total = BookmarksEntryServiceUtil.getGroupEntriesCount(scopeGroupId, groupEntriesUserId);
bookmarksSearchContainer.setTotal(total);
bookmarksSearchContainer.setResults(BookmarksEntryServiceUtil.getGroupEntries(scopeGroupId, groupEntriesUserId, bookmarksSearchContainer.getStart(), bookmarksSearchContainer.getEnd()));
...
\end{verbatim}
\item
By calling the \texttt{getScopeGroupId()} method on the request's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html}{\texttt{ThemeDisplay}}.
This method returns your app's current scope. For example, the Liferay
Blogs app's
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-web/src/main/java/com/liferay/blogs/web/internal/portlet/action/EditEntryMVCActionCommand.java\#L350-L362}{\texttt{EditEntryMVCActionCommand}}
class does this in its \texttt{subscribe} and \texttt{unsubscribe}
methods:
\begin{verbatim}
protected void subscribe(ActionRequest actionRequest) throws Exception {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
WebKeys.THEME_DISPLAY);
_blogsEntryService.subscribe(themeDisplay.getScopeGroupId());
}
protected void unsubscribe(ActionRequest actionRequest) throws Exception {
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(
WebKeys.THEME_DISPLAY);
_blogsEntryService.unsubscribe(themeDisplay.getScopeGroupId());
}
\end{verbatim}
If you know your app always needs the portal instance ID, use
\texttt{themeDisplay.getCompanyId()}.
\item
By calling the \texttt{getScopeGroupId()} method on a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/ServiceContext.html}{\texttt{ServiceContext}}
object. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/understanding-servicecontext}{Understanding
Service Context} for an example and more details. If you know your app
always needs the portal instance ID, use the \texttt{ServiceContext}
object's \texttt{getCompanyId()} method.
\end{enumerate}
\section{Accessing the Site Scope}\label{accessing-the-site-scope}
To access the Site scope regardless of your app's current scope, use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html}{\texttt{ThemeDisplay}}
method \texttt{getSiteGroupId()}. For more information on this use case,
see
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#accessing-the-site-scope-across-apps}{Accessing
the Site Scope Across Apps}.
For example, the Web Content app's
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/journal/journal-web/src/main/resources/META-INF/resources/edit_feed.jsp\#L40}{\texttt{edit\_feed.jsp}}
uses the \texttt{getSiteGroupId()} method to get the Site ID, which is
required to retrieve Structures:
\begin{verbatim}
ddmStructure = DDMStructureLocalServiceUtil.fetchStructure(themeDisplay.getSiteGroupId(),
PortalUtil.getClassNameId(JournalArticle.class), ddmStructureKey, true);
\end{verbatim}
\section{Related Topics}\label{related-topics-6}
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#data-scopes}{Data
Scopes}
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}
\href{/docs/7-2/appdev/-/knowledge_base/a/creating-a-service-builder-project}{Service
Builder Project}
\href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Business
Logic with Service Builder}
\chapter{Using the Message Bus}\label{using-the-message-bus}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Here, you'll learn how to use the
\href{/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks\#message-bus}{Message
Bus} to send and receive messages in the portal. The following topics
are covered:
\begin{itemize}
\tightlist
\item
\hyperref[messaging-destinations]{Messaging Destinations}
\item
\hyperref[message-listeners]{Message Listeners}
\item
\hyperref[sending-messages]{Sending Messages}
\end{itemize}
\section{Messaging Destinations}\label{messaging-destinations}
In Message Bus, you send messages to destinations. A destination is a
named logical (not physical) location. Sender classes send messages to
destinations, while listener classes wait to receive messages at the
destinations. In this way, the sender and recipient don't need to know
each other---they're loosely coupled.
\section{Destination Configuration}\label{destination-configuration}
Each destination has a name and type and can have several other
attributes. The destination type determines these things:
\begin{itemize}
\tightlist
\item
Whether there's a message queue.
\item
The kinds of threads involved with a destination.
\item
The message delivery behavior to expect at the destination.
\end{itemize}
Here are the primary destination types:
\textbf{Parallel Destination}
\begin{itemize}
\tightlist
\item
Messages sent here are queued.
\item
Multiple worker threads from a thread pool deliver each message to a
registered message listener. There's one worker thread per message per
message listener.
\end{itemize}
\textbf{Serial Destination}
\begin{itemize}
\tightlist
\item
Messages sent here are queued.
\item
Worker threads from a thread pool deliver the messages to each
registered message listener, one worker thread per message.
\end{itemize}
\textbf{Synchronous Destination}
\begin{itemize}
\tightlist
\item
Messages sent here are directly delivered to message listeners.
\item
The thread sending the message here also delivers the message to all
message listeners.
\end{itemize}
Preconfigured destinations exist for various purposes. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationNames.html}{\texttt{DestinationNames}}
class defines \texttt{String} constants for each. For example,
\texttt{DestinationNames.HOT\_DEPLOY} (value is
\texttt{"liferay/hot\_deploy"}) is for deployment event messages. Since
destinations are tuned for specific purposes, don't modify them.
Destinations are based on
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationConfiguration.html}{\texttt{DestinationConfiguration}}
instances. The configuration specifies the destination type, name, and
these destination-related attributes:
\textbf{Maximum Queue Size}: Limits the number of the destination's
queued messages.
\textbf{Rejected Execution Handler}: A
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/concurrent/RejectedExecutionHandler.html}{\texttt{RejectedExecutionHandler}}
instance can take action (e.g., log warnings) regarding rejected
messages when the destination queue is full.
\textbf{Workers Core Size}: Initial number of worker threads for
processing messages.
\textbf{Workers Max Size}: Limits the number of worker threads for
processing messages.
The \texttt{DestinationConfiguration} class provides these static
methods for creating the various types of configurations.
\begin{itemize}
\tightlist
\item
\texttt{createParallelDestinationConfiguration(String\ destinationName)}
\item
\texttt{createSerialDestinationConfiguration(String\ destinationName)}
\item
\texttt{createSynchronousDestinationConfiguration(String\ destinationName)}
\end{itemize}
You can also use the \texttt{DestinationConfiguration}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationConfiguration.html\#DestinationConfiguration-java.lang.String-java.lang.String-}{constructor}
to create a configuration for any destination type, even your own.
For instructions on creating your own destination, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-destination}{Creating
a Destination}.
\section{Message Listeners}\label{message-listeners}
If you're interested in messages sent to a destination, you need to
\emph{listen} for them. That is, you must create and register a message
listener for the destination.
To create a message listener, implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageListener.html}{\texttt{MessageListener}}
interface and override its \texttt{receive(Message)} method to process
messages your way.
\begin{verbatim}
public void receive(Message message) {
// Process messages your way
}
\end{verbatim}
Here are the ways to register your listener with Message Bus:
\textbf{Automatic Registration as a Component}: Publish the listener to
the OSGi registry as a
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{Declarative
Services} component that specifies a destination. Message Bus
automatically wires the listener to the destination.
\textbf{Registering via MessageBus}: Obtain and use a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBus.html}{\texttt{MessageBus}}
reference to directly register the listener to a destination.
\textbf{Registering Directly to a Destination}: Obtain a reference to a
specific destination and use it to directly register the listener with
that destination.
For instructions on these topics, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/registering-message-listeners}{Registering
Message Listeners}.
\section{Sending Messages}\label{sending-messages}
Message Bus lets you send messages to destinations that have any number
of listening classes. As a message sender you don't need to know the
message recipients. Instead, you focus on creating message content
(payload) and sending messages to destinations.
You can also send messages in a synchronous or asynchronous manner. The
synchronous option waits for a response that the message was received or
that it timed out. The asynchronous option gives you the ``fire and
forget'' behavior; send the message and continue processing without
waiting for a response.
See these topics for instructions on creating and sending messages:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message}{Creating
a Message}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/sending-a-message}{Sending
a Message}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/sending-messages-across-a-cluster}{Sending
Messages Across a Cluster}
\end{itemize}
\chapter{Creating a Destination}\label{creating-a-destination}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus\#messaging-destinations}{Message
Bus destinations} are based on destination configurations and registered
as OSGi services. Message Bus detects the destination services and
manages their associated destinations.
Here are the steps for creating a destination. The example configurator
class that follows demonstrates these steps.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an \texttt{activate(BundleContext)} method in your component.
Then create a
\href{https://osgi.org/javadoc/r4v43/core/org/osgi/framework/BundleContext.html}{\texttt{BundleContext}}
instance variable and set it to the \texttt{activate} method's
\texttt{BundleContext}:
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
_bundleContext = bundleContext;
}
private final BundleContext _bundleContext;
\end{verbatim}
You'll create and register your destination inside this
\texttt{activate} method. This ensures that the destination is
available upon service activation. Once the destination is registered,
Message Bus detects its service and manages the destination.
\item
Create a destination configuration by using one of
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationConfiguration.html}{\texttt{DestinationConfiguration}'s}
static \texttt{create*} methods or its constructor. Set any attributes
that apply to the destinations you'll create with the destination
configuration.
For example, this code uses the \texttt{DestinationConfiguration}
constructor to create a destination configuration for parallel
destinations. It then sets the destination configuration's maximum
queue size and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/concurrent/RejectedExecutionHandler.html}{\texttt{RejectedExecutionHandler}}:
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
...
// Create a DestinationConfiguration for parallel destinations.
DestinationConfiguration destinationConfiguration =
new DestinationConfiguration(
DestinationConfiguration.DESTINATION_TYPE_PARALLEL,
"myDestinationName");
// Set the DestinationConfiguration's max queue size and
// rejected execution handler.
destinationConfiguration.setMaximumQueueSize(_MAXIMUM_QUEUE_SIZE);
RejectedExecutionHandler rejectedExecutionHandler =
new CallerRunsPolicy() {
@Override
public void rejectedExecution(
Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
if (_log.isWarnEnabled()) {
_log.warn(
"The current thread will handle the request " +
"because the graph walker's task queue is at " +
"its maximum capacity");
}
super.rejectedExecution(runnable, threadPoolExecutor);
}
};
destinationConfiguration.setRejectedExecutionHandler(
rejectedExecutionHandler);
}
\end{verbatim}
\item
Create the destination by invoking the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationFactory.html}{\texttt{DestinationFactory}}
method \texttt{createDestination(DestinationConfiguration)}, passing
in the destination configuration from the previous step.
For example, this code does so via a \texttt{DestinationFactory}
reference:
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
...
Destination destination = _destinationFactory.createDestination(
destinationConfiguration);
}
...
@Reference
private DestinationFactory _destinationFactory;
\end{verbatim}
\item
Register the destination as an OSGi service by invoking the
\texttt{BundleContext} method \texttt{registerService} with these
parameters:
\begin{itemize}
\tightlist
\item
The destination class \texttt{Destination.class}.
\item
Your \texttt{Destination} object.
\item
A \texttt{Dictionary} of properties defining the destination,
including the \texttt{destination.name}.
\end{itemize}
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
...
Dictionary properties = new HashMapDictionary<>();
properties.put("destination.name", destination.getName());
ServiceRegistration serviceRegistration =
_bundleContext.registerService(
Destination.class, destination, properties);
}
\end{verbatim}
\item
Manage the destination object and service registration resources using
a collection such as a
\texttt{Map\textless{}String,\ ServiceRegistration\textless{}Destination\textgreater{}\textgreater{}}.
Keeping references to these resources is helpful for when you're ready
to unregister and destroy them.
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
...
_serviceRegistrations.put(destination.getName(),
serviceRegistration);
}
...
private final Map>
_serviceRegistrations = new HashMap<>();
\end{verbatim}
\item
Add a \texttt{deactivate} method that unregisters and destroys any
destinations for this component. This ensures there aren't any active
destinations for this component when the service deactivates:
\begin{verbatim}
@Deactivate
protected void deactivate() {
// Unregister and destroy destinations
for (ServiceRegistration serviceRegistration :
_serviceRegistrations.values()) {
Destination destination = _bundleContext.getService(
serviceRegistration.getReference());
serviceRegistration.unregister();
destination.destroy();
}
_serviceRegistrations.clear();
}
\end{verbatim}
\end{enumerate}
Here's the full messaging configurator component class that contains the
code in the above steps:
\begin{verbatim}
@Component (
immediate = true,
service = MyMessagingConfigurator.class
)
public class MyMessagingConfigurator {
@Activate
protected void activate(BundleContext bundleContext) {
_bundleContext = bundleContext;
// Create a DestinationConfiguration for parallel destinations.
DestinationConfiguration destinationConfiguration =
new DestinationConfiguration(
DestinationConfiguration.DESTINATION_TYPE_PARALLEL,
"myDestinationName");
// Set the DestinationConfiguration's max queue size and
// rejected execution handler.
destinationConfiguration.setMaximumQueueSize(_MAXIMUM_QUEUE_SIZE);
RejectedExecutionHandler rejectedExecutionHandler =
new CallerRunsPolicy() {
@Override
public void rejectedExecution(
Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
if (_log.isWarnEnabled()) {
_log.warn(
"The current thread will handle the request " +
"because the graph walker's task queue is at " +
"its maximum capacity");
}
super.rejectedExecution(runnable, threadPoolExecutor);
}
};
destinationConfiguration.setRejectedExecutionHandler(
rejectedExecutionHandler);
// Create the destination
Destination destination = _destinationFactory.createDestination(
destinationConfiguration);
// Add the destination to the OSGi service registry
Dictionary properties = new HashMapDictionary<>();
properties.put("destination.name", destination.getName());
ServiceRegistration serviceRegistration =
_bundleContext.registerService(
Destination.class, destination, properties);
// Track references to the destination service registrations
_serviceRegistrations.put(destination.getName(),
serviceRegistration);
}
@Deactivate
protected void deactivate() {
// Unregister and destroy destinations this component unregistered
for (ServiceRegistration serviceRegistration :
_serviceRegistrations.values()) {
Destination destination = _bundleContext.getService(
serviceRegistration.getReference());
serviceRegistration.unregister();
destination.destroy();
}
_serviceRegistrations.clear();
}
private final BundleContext _bundleContext;
@Reference
private DestinationFactory _destinationFactory;
private final Map>
_serviceRegistrations = new HashMap<>();
}
\end{verbatim}
\section{Related Topics}\label{related-topics-7}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus\#messaging-destinations}{Message
Bus Destinations}
\chapter{Message Bus Event Listeners}\label{message-bus-event-listeners}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
When
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus}{using
Message Bus}, you may wish to listen for events that take place within
the Message Bus framework itself, independent of messages. For example,
you can listen for when
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus\#messaging-destinations}{destinations}
and
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus\#message-listeners}{message
listeners} are added or removed. Here, you'll learn how.
\section{Listening for Destinations}\label{listening-for-destinations}
Message Bus notifies event listeners when destinations are added and
removed. To register these listeners, publish a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBusEventListener.html}{\texttt{MessageBusEventListener}}
instance to the OSGi service registry (e.g., via an \texttt{@Component}
annotation).
Here's an example implementation of \texttt{MessageBusEventListener}.
Use the \texttt{destinationAdded} and \texttt{destinationDestroyed}
methods to implement any logic that you want to run when a destination
is added or removed, respectively:
\begin{verbatim}
@Component(
immediate = true,
service = MessageBusEventListener.class
)
public class MyMessageBusEventListener implements MessageBusEventListener {
void destinationAdded(Destination destination) {
...
}
void destinationDestroyed(Destination destination) {
...
}
}
\end{verbatim}
\section{Listening for Message
Listeners}\label{listening-for-message-listeners}
Message Bus notifies
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationEventListener.html}{\texttt{DestinationEventListener}}
instances when message listeners for destinations are either registered
or unregistered. To register an event listener to a destination, publish
a \texttt{DestinationEventListener} service to the OSGi service
registry, making sure to specify the destination's
\texttt{destination.name} property.
\begin{verbatim}
@Component(
immediate = true,
property = {"destination.name=myCustom/Destination"},
service = DestinationEventListener.class
)
public class MyDestinationEventListener implements DestinationEventListener {
void messageListenerRegistered(String destinationName,
MessageListener messageListener) {
...
}
void messageListenerUnregistered(String destinationName,
MessageListener messageListener) {
...
}
}
\end{verbatim}
\section{Related Topics}\label{related-topics-8}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus}{Using
the Message Bus}
\chapter{Registering Message
Listeners}\label{registering-message-listeners}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
There are three ways to register a
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus\#message-listeners}{message
listener} with the Message Bus:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
\hyperref[automatic-registration-as-a-component]{Automatic
Registration as a Component}
\item
\hyperref[registering-via-a-messagebus-reference]{Registering via a
MessageBus Reference}
\item
\hyperref[registering-directly-to-the-destination]{Registering
Directly to the Destination}
\end{enumerate}
Automatic registration as a component is the preferred way to register
message listeners to destinations. You might want to use the other two
ways if, for example, you want to create some special proxy wrappers.
\noindent\hrulefill
\textbf{Note}: The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationNames.html}{\texttt{DestinationNames}}
class defines \texttt{String} constants for Liferay DXP's preconfigured
destinations.
\noindent\hrulefill
\section{Automatic Registration as a
Component}\label{automatic-registration-as-a-component}
You can specify a message listener in the
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{Declarative
Services} \texttt{@Component} annotation:
\begin{verbatim}
@Component (
immediate = true,
property = {"destination.name=myCustom/Destination"},
service = MessageListener.class
)
public class MyMessageListener implements MessageListener {
...
public void receive(Message message) {
// Handle the message
}
}
\end{verbatim}
The Message Bus listens for
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageListener.html}{\texttt{MessageListener}}
service components like this one to publish themselves to the OSGi
service registry. The attribute \texttt{immediate\ =\ true} tells the
OSGi framework to activate the component as soon as its dependencies
resolve. Message Bus wires each registered listener to the destination
its \texttt{destination.name} property specifies. If the destination is
not yet registered, Message Bus queues the listener until the
destination registers.
\section{Registering via a MessageBus
Reference}\label{registering-via-a-messagebus-reference}
You can use a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBus.html}{\texttt{MessageBus}}
reference to directly register message listeners to destinations. Here's
a registrator that demonstrates this:
\begin{verbatim}
@Component (
immediate = true,
service = MyMessageListenerRegistrator.class
)
public class MyMessageListenerRegistrator {
...
@Activate
protected void activate() {
_messageListener = new MessageListener() {
public void receive(Message message) {
// Handle the message
}
};
_messageBus.registerMessageListener("myDestinationName",
_messageListener);
}
@Deactivate
protected void deactivate() {
_messageBus.unregisterMessageListener("myDestinationName",
_messageListener);
}
@Reference
private MessageBus _messageBus;
private MessageListener _messageListener;
}
\end{verbatim}
The \texttt{\_messageBus} field's \texttt{@Reference} annotation binds
it to the \texttt{MessageBus} instance. The \texttt{activate} method
creates the listener and uses the Message Bus to register the listener
to a destination named \texttt{"myDestination"}. When this registrator
component is destroyed, the \texttt{deactivate} method unregisters the
listener.
\section{Registering Directly to the
Destination}\label{registering-directly-to-the-destination}
You can use a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/Destination.html}{\texttt{Destination}}
reference to register a listener to that destination. Here's a
registrator that demonstrates this:
\begin{verbatim}
@Component (
immediate = true,
service = MyMessageListenerRegistrator.class
)
public class MyMessageListenerRegistrator {
...
@Activate
protected void activate() {
_messageListener = new MessageListener() {
public void receive(Message message) {
// Handle the message
}
};
_destination.register(_messageListener);
}
@Deactivate
protected void deactivate() {
_destination.unregister(_messageListener);
}
@Reference(target = "(destination.name=someDestination)")
private Destination _destination;
private MessageListener _messageListener;
}
\end{verbatim}
The \texttt{\_destination} field's \texttt{@Reference} annotation binds
it to a destination named \texttt{someDestination}. The
\texttt{activate} method creates the listener and registers it to the
destination. When this registrator component is destroyed, the
\texttt{deactivate} method unregisters the listener.
\section{Related Topics}\label{related-topics-9}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus}{Using
the Message Bus}
\chapter{Creating a Message}\label{creating-a-message}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Before you can
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus\#sending-messages}{send
a message} via the Message Bus, you must first create it. Here's how to
create a message:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Call the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/Message.html}{\texttt{Message}}
constructor to create a new \texttt{Message}:
\begin{verbatim}
Message message = new Message();
\end{verbatim}
\item
Populate the message with a \texttt{String} or \texttt{Object}
payload:
\begin{itemize}
\item
String payload:
\texttt{message.setPayload("Message\ Bus\ is\ great!")}
\item
Object payload: \texttt{message.put("firstName",\ "Joe")}
\end{itemize}
\item
To receive responses at a particular location, set both of these
attributes:
\begin{itemize}
\item
Response destination name:
\texttt{setResponseDestinationName(String)}
\item
Response ID: \texttt{setResponseId(String)}
\end{itemize}
\end{enumerate}
\section{Related Topics}\label{related-topics-10}
\href{/docs/7-2/frameworks/-/knowledge_base/f/sending-a-message}{Sending
a Message}
\href{/docs/7-2/frameworks/-/knowledge_base/f/sending-messages-across-a-cluster}{Sending
Messages Across a Cluster}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus}{Using
the Message Bus}
\chapter{Sending a Message}\label{sending-a-message}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Once you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message}{created
a message}, there are three ways to send it with the
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus}{Message
Bus}:
\begin{itemize}
\tightlist
\item
\hyperref[directly-with-messagebus]{Directly with \texttt{MessageBus}}
\item
\hyperref[asynchronously-with-singledestinationmessagesender]{Asynchronously
with \texttt{SingleDestinationMessageSender}}
\item
\hyperref[synchronously-with-synchronousmessagesender]{Synchronously
with \texttt{SynchronousMessageSender}}
\end{itemize}
\section{Directly with MessageBus}\label{directly-with-messagebus}
To send a message directly with
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBus.html}{\texttt{MessageBus}},
follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a \texttt{MessageBus} reference:
\begin{verbatim}
@Reference
private MessageBus _messageBus;
\end{verbatim}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message}{Create
a message}. For example:
\begin{verbatim}
Message message = new Message();
message.put("myId", 12345);
message.put("someAttribute", "abcdef");
\end{verbatim}
\item
Call the \texttt{MessageBus} reference's \texttt{sendMessage} method
with the destination and message:
\begin{verbatim}
_messageBus.sendMessage("myDestinationName", message);
\end{verbatim}
\end{enumerate}
Here's a class that contains this example:
\begin{verbatim}
@Component(
immediate = true,
service = SomeServiceImpl.class
)
public class SomeServiceImpl {
...
public void sendSomeMessage() {
Message message = new Message();
message.put("myId", 12345);
message.put("someAttribute", "abcdef");
_messageBus.sendMessage("myDestinationName", message);
}
@Reference
private MessageBus _messageBus;
}
\end{verbatim}
\section{Asynchronously with
SingleDestinationMessageSender}\label{asynchronously-with-singledestinationmessagesender}
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SingleDestinationMessageSender.html}{\texttt{SingleDestinationMessageSender}}
interface wraps the Message Bus to send messages asynchronously. Follow
these steps to use this interface to send asynchronous messages:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SingleDestinationMessageSenderFactory.html}{\texttt{SingleDestinationMessageSenderFactory}}
reference:
\begin{verbatim}
@Reference
private SingleDestinationMessageSenderFactory _messageSenderFactory;
\end{verbatim}
\item
Create a \texttt{SingleDestinationMessageSender} by calling the
\texttt{SingleDestinationMessageSenderFactory} reference's
\texttt{createSingleDestinationMessageSender} method with the
message's destination:
\begin{verbatim}
SingleDestinationMessageSender messageSender =
_messageSenderFactory.createSingleDestinationMessageSender("myDestinationName");
\end{verbatim}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message}{Create
a message}. For example:
\begin{verbatim}
Message message = new Message();
message.put("myId", 12345);
message.put("someValue", "abcdef");
\end{verbatim}
\item
Send the message by calling the
\texttt{SingleDestinationMessageSender} instance's \texttt{send}
method with the message:
\begin{verbatim}
messageSender.send(message);
\end{verbatim}
\end{enumerate}
Here's a class that contains this example:
\begin{verbatim}
@Component(
immediate = true,
service = SomeServiceImpl.class
)
public class SomeServiceImpl {
...
public void sendSomeMessage() {
SingleDestinationMessageSender messageSender =
_messageSenderFactory.createSingleDestinationMessageSender("myDestinationName");
Message message = new Message();
message.put("myId", 12345);
message.put("someValue", "abcdef");
messageSender.send(message);
}
@Reference
private SingleDestinationMessageSenderFactory _messageSenderFactory;
}
\end{verbatim}
\section{Synchronously with
SynchronousMessageSender}\label{synchronously-with-synchronousmessagesender}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SynchronousMessageSender.html}{\texttt{SynchronousMessageSender}}
sends a message to the Message Bus and blocks until receiving a response
or the response times out. A \texttt{SynchronousMessageSender} has these
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SynchronousMessageSender.Mode.html}{operating
modes}:
\texttt{DEFAULT}: Delivers the message in a separate thread and also
provides timeouts, in case the message is not delivered properly.
\texttt{DIRECT}: Delivers the message in the same thread of execution
and blocks until it receives a response.
Follow these steps to send a synchronous message with
\texttt{SynchronousMessageSender}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SingleDestinationMessageSenderFactory.html}{\texttt{SingleDestinationMessageSenderFactory}}
reference:
\begin{verbatim}
@Reference
private SingleDestinationMessageSenderFactory _messageSenderFactory;
\end{verbatim}
\item
Create a \texttt{SingleDestinationSynchronousMessageSender} by calling
the \texttt{SingleDestinationMessageSenderFactory} reference's
\texttt{createSingleDestinationSynchronousMessageSender} method with
the destination and operating mode. Note that this example uses the
\texttt{DEFAULT} mode:
\begin{verbatim}
SingleDestinationSynchronousMessageSender messageSender =
_messageSenderFactory.createSingleDestinationSynchronousMessageSender(
"myDestinationName", SynchronousMessageSender.Mode.DEFAULT);
\end{verbatim}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message}{Create
a message}. For example:
\begin{verbatim}
Message message = new Message();
message.put("myId", 12345);
message.put("someValue", "abcdef");
\end{verbatim}
\item
Send the message by calling the
\texttt{SingleDestinationSynchronousMessageSender} instance's
\texttt{send} method with the message:
\begin{verbatim}
messageSender.send(message);
\end{verbatim}
\end{enumerate}
Here's a class that contains this example:
\begin{verbatim}
@Component(
immediate = true,
service = SomeServiceImpl.class
)
public class SomeServiceImpl {
...
public void sendSomeMessage() {
Message message = new Message();
message.put("myId", 12345);
message.put("someAttribute", "abcdef");
SingleDestinationSynchronousMessageSender messageSender =
_messageSenderFactory.createSingleDestinationSynchronousMessageSender(
"myDestinationName", SynchronousMessageSender.Mode.DEFAULT);
messageSender.send(message);
}
@Reference
private SingleDestinationMessageSenderFactory _messageSenderFactory;
}
\end{verbatim}
\section{Related Topics}\label{related-topics-11}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message}{Creating
a Message}
\href{/docs/7-2/frameworks/-/knowledge_base/f/sending-messages-across-a-cluster}{Sending
Messages Across a Cluster}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus}{Using
the Message Bus}
\chapter{Sending Messages Across a
Cluster}\label{sending-messages-across-a-cluster}
To ensure a message sent to a destination is received by all cluster
nodes, you must register a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cluster/messaging/ClusterBridgeMessageListener.html}{\texttt{ClusterBridgeMessageListener}}
at that destination. This bridges the local destination to the cluster
and ensures that messages sent to the destination are distributed across
the cluster's JVMs. You should do this in a registrator class, like
those shown in
\href{/docs/7-2/frameworks/-/knowledge_base/f/registering-message-listeners}{Registering
Message Listeners}.
Follow these steps to create a registrator class that registers a
\texttt{ClusterBridgeMessageListener} to a destination:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create the registrator class as an OSGi component:
\begin{verbatim}
@Component(
immediate = true,
service = MyMessageListenerRegistrator.class
)
public class MyMessageListenerRegistrator {
...
}
\end{verbatim}
\item
Create a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageListener.html}{\texttt{MessageListener}}
variable:
\begin{verbatim}
private MessageListener _clusterBridgeMessageListener;
\end{verbatim}
\item
Create a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/Destination.html}{\texttt{Destination}}
reference and set its \texttt{destination.name} property to your
destination. For example, this reference is for the destination
\texttt{liferay/live\_users}:
\begin{verbatim}
@Reference(target = "(destination.name=liferay/live_users)")
private Destination _destination;
\end{verbatim}
\item
In the registrator's \texttt{activate} method, create a new
\texttt{ClusterBridgeMessageListener} and set it to the
\texttt{MessageListener} variable you created earlier. Then set the
\texttt{ClusterBridgeMessageListener}'s priority and register the
\texttt{ClusterBridgeMessageListener} to the destination:
\begin{verbatim}
@Activate
protected void activate() {
_clusterBridgeMessageListener = new ClusterBridgeMessageListener();
_clusterBridgeMessageListener.setPriority(Priority.LEVEL5)
_destination.register(_clusterBridgeMessageListener);
}
\end{verbatim}
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cluster/Priority.html}{\texttt{Priority}}
enum has ten levels (\texttt{Level1} through \texttt{Level10}, with
\texttt{Level10} being the most important). Each level is a priority
queue for sending messages through the cluster. This is similar in
concept to thread priorities: \texttt{Thread.MIN\_PRIORITY},
\texttt{Thread.MAX\_PRIORITY}, and \texttt{Thread.NORM\_PRIORITY}.
\item
In the registrator's \texttt{deactivate} method, unregister the
\texttt{ClusterBridgeMessageListener} from the destination:
\begin{verbatim}
@Deactivate
protected void deactivate() {
_destination.unregister(_clusterBridgeMessageListener);
}
\end{verbatim}
\end{enumerate}
Here's the full registrator class for this example:
\begin{verbatim}
@Component(
immediate = true,
service = MyMessageListenerRegistrator.class
)
public class MyMessageListenerRegistrator {
...
@Activate
protected void activate() {
_clusterBridgeMessageListener = new ClusterBridgeMessageListener();
_clusterBridgeMessageListener.setPriority(Priority.LEVEL5)
_destination.register(_clusterBridgeMessageListener);
}
@Deactivate
protected void deactivate() {
_destination.unregister(_clusterBridgeMessageListener);
}
@Reference(target = "(destination.name=liferay/live_users)")
private Destination _destination;
private MessageListener _clusterBridgeMessageListener;
}
\end{verbatim}
\section{Related Topics}\label{related-topics-12}
\href{/docs/7-2/frameworks/-/knowledge_base/f/registering-message-listeners}{Registering
Message Listeners}
\href{/docs/7-2/frameworks/-/knowledge_base/f/sending-a-message}{Sending
a Message}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus}{Using
the Message Bus}
\chapter{Cache Configuration}\label{cache-configuration}
Caching makes specified data readily available in memory. It costs
memory but improves performance. You can experiment with cache to
determine what's good for your system. If your site serves lots of web
content articles, for example, you may want to increase the limit on how
many you can cache.
Liferay's cache configuration framework uses
\href{https://www.ehcache.org/}{Ehcache}. It's an independent framework
used by Liferay DXP's data access and template engine components. It
manages two pools:
\textbf{Multi-VM:} Cache is replicated among cluster nodes.
\texttt{EntityCache} and \texttt{FinderCache} (described next) are in
this pool because they must synchronize with data on all nodes.
\textbf{Single-VM:} Cache is managed uniquely per VM and isn't
replicated among nodes. Single-VM cache is for objects and references
that you don't need/want replicated among nodes.
Here are ways you can configure the Ehcache:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache}{Overriding
Cache}: Tuning existing cache.
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/caching-data}{Caching
Data}: Implementing cache for custom data.
\end{itemize}
Start learning the Liferay cache configuration basics here.
\section{Cache Types}\label{cache-types}
You can cache any classes you like. Conveniently, Liferay DXP caches
\href{/docs/7-2/appdev/-/knowledge_base/a/defining-service-entities}{service
entities} and
\href{/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods}{service
entity finder results} automatically by default.
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} generates their caching code in the
\href{/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder}{service
persistence layer}. The code operates on these cache types:
\textbf{\texttt{EntityCache}:} Holds service entities by primary keys.
The caching code maps entity primary keys to implementation objects. An
entity's \texttt{*PersistenceImpl.fetchByPrimaryKey} method uses
\texttt{EntityCache}.
\textbf{\texttt{FinderCache}:} Holds parameterized service entity search
results. The caching code associates
\href{/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods}{service
entity finder} query parameter values with matching entity results.
There's code for caching entities, paginated entity lists, and
non-paginated entity lists that match your finder parameters. An
entity's \texttt{fetchByValue}, \texttt{findByValue},
\texttt{countByValue}, \texttt{findAll}, and \texttt{countAll} methods
use the FinderCache.
\section{Cache Configuration}\label{cache-configuration-1}
Liferay DXP designates separate cache configurations for multi-VM and
single-VM environments. Default \texttt{EntityCache} and
\texttt{FinderCache} are specified programmatically, while Liferay's
global cache configuration and custom cache configurations are specified
via files. All configurations adhere to the
\href{http://www.ehcache.org/ehcache.xsd}{Ehcache XSD}.
Liferay's global cache configuration is processed first on startup.
Cache configurations in modules and WARs are processed as they're
deployed after the initial global cache configuration.
\section{Initial Global Cache
Configuration}\label{initial-global-cache-configuration}
Liferay's portal cache implementation LPKG file
(\texttt{Liferay\ {[}version{]}\ Foundation\ -\ Liferay\ {[}version{]}\ Portal\ Cache\ -\ Impl.lpkg})
found in the \texttt{{[}Liferay\_Home{]}/osgi/marketplace} folder
contains the initial global cache configuration. The LPKG file's
\texttt{com.liferay.portal.cache.ehcache.impl-{[}version{]}.jar} holds
the configuration files:
\begin{itemize}
\tightlist
\item
\texttt{liferay-multi-vm.xml}: Maps to the multi-VM pool.
\item
\texttt{liferay-single-vm.xml}: Maps to the single-VM pool.
\end{itemize}
\section{Module Cache Configuration}\label{module-cache-configuration}
Modules can configure (add or override) cache using configuration files
in their \texttt{src/main/resources/META-INF} folder:
\begin{itemize}
\tightlist
\item
\texttt{module-multi-vm.xml}: Maps to the multi-VM cache manager.
\item
\texttt{module-single-vm.xml}: Maps to the single-VM cache manager.
\end{itemize}
For example, the Liferay DXP Web Experience suite's
\texttt{com.liferay.journal.service} module uses the following
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/journal/journal-service/src/main/resources/META-INF/module-multi-vm.xml}{\texttt{module-multi-vm.xml}}
to create a cache named \texttt{com.liferay.journal.util.JournalContent}
in the multi-VM pool.
\begin{verbatim}
\end{verbatim}
Portlet WARs can configure cache too.
\section{Portlet WAR Cache
Configuration}\label{portlet-war-cache-configuration}
Ehcache configuration in a portlet WAR has these requirements:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The Ehcache configuration XML file must be in the application context
(e.g., any path under \texttt{WEB-INF/src}).
\item
The \texttt{portlet.properties} file must specify the cache file
location. Either of the two properties is used and is assigned the
cache file path, relative to the application context root (e.g.,
\texttt{WEB-INF/src}).
\end{enumerate}
\begin{verbatim}
ehcache.single.vm.config.location=path/to/single/vm/config/file
ehcache.multi.vm.config.location=path/to/multi/vm/config/file
\end{verbatim}
For example, here's the
\href{https://github.com/liferay/liferay-plugins/blob/7.0.x/portlets/test-cache-configuration-portlet}{\texttt{test-cache-configuration-portlet}}
WAR's structure:
\begin{itemize}
\tightlist
\item
\texttt{docroot/WEB-INF/src/}
\begin{itemize}
\tightlist
\item
\texttt{ehcache/}
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-plugins/blob/7.0.x/portlets/test-cache-configuration-portlet/docroot/WEB-INF/src/ehcache/liferay-single-vm-ext.xml}{\texttt{liferay-single-vm-ext.xml}}
\item
\href{https://github.com/liferay/liferay-plugins/blob/7.0.x/portlets/test-cache-configuration-portlet/docroot/WEB-INF/src/ehcache/liferay-multi-vm-clustered-ext.xml}{\texttt{liferay-multi-vm-clustered-ext.xml}}
\end{itemize}
\item
\texttt{portlet.properties}
\end{itemize}
\end{itemize}
The \texttt{portlet.properties} file specifies these properties:
\begin{verbatim}
ehcache.single.vm.config.location=ehcache/liferay-single-vm-ext.xml
ehcache.multi.vm.config.location=ehcache/liferay-multi-vm-clustered-ext.xml
\end{verbatim}
\section{Cache Names and
Registration}\label{cache-names-and-registration}
A cache is identified by its name (e.g.,
\texttt{\textless{}cache\ name="com.liferay.docs.MyClass"\ ...\ /\textgreater{}}).
If a module provides a cache configuration with the name of an existing
cache, the existing cache is overridden. If a module provides a cache
configuration with a new name, a new cache is added.
Here's what happens behind the scenes: Liferay's cache manager checks
the configurations. If a cache with the name already exists, the cache
manager removes it from Ehcache's cache registry and registers a new
Ehcache into Ehcache's cache registry. If the name is new, the Liferay
cache manager just registers a new Ehcache.
Cache names are arbitrary except for \texttt{EntityCache} and
\texttt{FinderCache}.
\section{EntityCache Names}\label{entitycache-names}
\texttt{EntityCache} uses this naming convention:
\texttt{PREFIX\ +\ ENTITY\_IMPL\_CLASS\_NAME}
where the \texttt{PREFIX} is always this:
\begin{verbatim}
com.liferay.portal.kernel.dao.orm.EntityCache.
\end{verbatim}
For example, the cache name for the
\texttt{com.liferay.portal.kernel.model.User} entity starts with the
\texttt{PREFIX} and ends with the implementation class name
\texttt{com.liferay.portal.model.impl.UserImpl}:
\begin{verbatim}
com.liferay.portal.kernel.dao.orm.EntityCache.com.liferay.portal.model.impl.UserImpl
\end{verbatim}
\section{FinderCache Names}\label{findercache-names}
\texttt{FinderCache} uses this naming convention:
\texttt{PREFIX\ +\ ENTITY\_IMPL\_CLASS\_NAME\ +\ {[}".LIST1"\textbar{}".LIST2"{]}}
where the \texttt{PREFIX} is always this:
\begin{verbatim}
com.liferay.portal.kernel.dao.orm.FinderCache.
\end{verbatim}
Here are the \texttt{FinderCache} types and their name patterns.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2222}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3889}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3889}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Type
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Pattern
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Example
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Entity instances matching query parameters. &
\texttt{PREFIX\ +\ ENTITY\_IMPL\_CLASS\_NAME} &
\texttt{com.liferay.portal.kernel.dao.orm.FinderCache.com.liferay.portal.model.impl.ClassNameImpl} \\
Paginated lists of entity instances matching query parameters. &
\texttt{PREFIX\ +\ ENTITY\_IMPL\_CLASS\_NAME\ +\ ".List1"} &
\texttt{com.liferay.portal.kernel.dao.orm.FinderCache.com.liferay.portal.model.impl.ClassNameImpl.List1} \\
Non-paginated lists of entity instances matching query parameters. &
\texttt{PREFIX\ +\ ENTITY\_IMPL\_CLASS\_NAME\ +\ ".List2"} &
\texttt{com.liferay.portal.kernel.dao.orm.FinderCache.com.liferay.portal.model.impl.ClassNameImpl.List2} \\
\end{longtable}
\noindent\hrulefill
Now that you have a basic understanding of cache in Liferay, continue
with overriding an existing cache configuration or caching custom data.
\chapter{Overriding Cache}\label{overriding-cache}
Liferay DXP pre-configures cache for service entities, service entity
finder results, and cache for several other classes. You can tune
existing cache to meet your needs. For example, it may help to write
cache overflow elements to disk, increase the maximum number of cached
elements, or make other adjustments. Using a module and only one XML
file, you can override cache configurations dynamically.
\noindent\hrulefill
\textbf{Warning:} Modifying an Ehcache element flushes its cache.
\noindent\hrulefill
Here is how to override a cache configuration:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Identify the name of the cache you want to override. Existing cache
configurations and statistics (hit/miss counts and percentages) can be
examined at runtime through JMX. Using a tool that supports JMX
analysis, you can examine Liferay DXP's cache configurations in the
MBean of \texttt{net.sf.ehcache}. Please note that the caches listed
in the MBean are more than what Liferay DXP's cache configuration
files specify because some caches are created purely through Java
code.
\begin{figure}
\centering
\includegraphics{./images/zulu-mission-control.png}
\caption{Caches configured in Liferay DXP can be examined using JMX
tools such as Zulu Mission Control (Portal Process → MBean server →
MBean Browser)}
\end{figure}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** See
[Cache Names and Registration](/docs/7-2/frameworks/-/knowledge_base/f/cache-configuration#cache-names-and-registration)
to identify `EntityCache` and the different kinds of `FinderCache` instances
associated with service entities.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
Some cache configurations can also be viewed statically in their deployment
artifacts or source code.
- `liferay-*-vm.xml` files in the
`Liferay [version] Foundation - Liferay [version] Portal Cache - Impl.lpkg` file.
- `module-*-vm.xml` files in modules or Liferay LPKG files.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
If you don't own the existing project that specifies the cache or you
want to use a different project to configure the cache, create a
module project. Otherwise, edit the cache in the existing project.
These instructions demonstrate adding the cache configuration to a new
module project.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Tip:** create new projects using the
[API project template](/docs/7-2/reference/-/knowledge_base/r/api-template)
and remove the Java class generated in the `src/main/java/` folder.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
In the \texttt{src/main/resources/META-INF} folder, add an XML file
for the type of cache (multi-VM or single-VM) you're overriding.
\texttt{module-multi-vm.xml} file:
\begin{verbatim}
\end{verbatim}
\texttt{module-single-vm.xml} file:
\begin{verbatim}
\end{verbatim}
\item
In the \texttt{\textless{}ehcache/\textgreater{}} element, add a
\texttt{\textless{}cache/\textgreater{}} element and set its
\texttt{name} attribute to the name of the cache you're overriding.
\item
Specify all existing \texttt{\textless{}cache/\textgreater{}} element
attributes you want to preserve. Hint: view the attributes in an MBean
browser, as mentioned earlier.
\item
Add or modify attributes to meet your needs. The
\texttt{\textless{}cache/\textgreater{}} element attributes are
described in the
\href{http://www.ehcache.org/ehcache.xsd}{ehcache.xsd} and
\href{http://www.ehcache.org/documentation/2.8/configuration/index.html}{Ehcache
documentation}.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy
the project}.
\end{enumerate}
Congratulations! Your cache modification is in effect.
\section{Related Topics}\label{related-topics-13}
\href{/docs/7-2/frameworks/-/knowledge_base/f/caching-data}{Caching
Data}
\chapter{Caching Data}\label{caching-data}
\href{/docs/7-2/frameworks/-/knowledge_base/f/cache-configuration}{Liferay's
caching framework} helps you use Ehcache to cache any data. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/SingleVMPool.html}{\texttt{SingleVMPool}}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/MultiVMPool.html}{\texttt{MultiVMPool}}
classes use Liferay's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/PortalCache.html}{\texttt{PortalCache}}
utility. Storing and retrieving cached data objects is as easy as using
a hash map: you associate a key with every cache value. The following
steps demonstrate implementing data caching.
\noindent\hrulefill
\textbf{Note:} If you want to modify cache for Service Builder Service
Entities or Entity Finder results, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache}{Overriding
Cache}.
\noindent\hrulefill
\section{Step 1: Determine Cache Pool
Requirements}\label{step-1-determine-cache-pool-requirements}
There are cache pools for single-VM and multi-VM environments. The pool
types and some Ehcache features require using \texttt{Serializable}
values.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Determine whether to create a cache
\href{/docs/7-2/frameworks/-/knowledge_base/f/cache-configuration}{in
a single VM or across multiple VMs} (e.g., in a clustered
environment).
\item
Determine if it's necessary to serialize the data you're caching.
\begin{itemize}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/MultiVMPool.html}{\texttt{MultiVMPool}}
requires both the cache key and cache value to be
\href{https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html}{\texttt{Serializable}}.
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/SingleVMPool.html}{\texttt{SingleVMPool}}
typically requires only cache keys to be
\href{https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html}{\texttt{Serializable}}.
Note that some Ehache features, such as \texttt{overflowToDisk},
require \texttt{Serializable} values too.
\end{itemize}
\end{enumerate}
\section{Step 2: Implement a Cache
Key}\label{step-2-implement-a-cache-key}
Cache keys must be unique,
\href{https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html}{\texttt{Serializable}}
objects. They should relate to the values being cached. For example, in
Liferay DXP's \texttt{JournalContentImpl}, a \texttt{JournalContentKey}
instance relates to each cached \texttt{JournalArticleDisplay} object.
Here's the \texttt{JournalContentKey} class:
\begin{verbatim}
private static class JournalContentKey implements Serializable {
@Override
public boolean equals(Object obj) {
JournalContentKey journalContentKey = (JournalContentKey)obj;
if ((journalContentKey._groupId == _groupId) &&
Objects.equals(journalContentKey._articleId, _articleId) &&
(journalContentKey._version == _version) &&
Objects.equals(
journalContentKey._ddmTemplateKey, _ddmTemplateKey) &&
(journalContentKey._layoutSetId == _layoutSetId) &&
Objects.equals(journalContentKey._viewMode, _viewMode) &&
Objects.equals(journalContentKey._languageId, _languageId) &&
(journalContentKey._page == _page) &&
(journalContentKey._secure == _secure)) {
return true;
}
return false;
}
@Override
public int hashCode() {
int hashCode = HashUtil.hash(0, _groupId);
hashCode = HashUtil.hash(hashCode, _articleId);
hashCode = HashUtil.hash(hashCode, _version);
hashCode = HashUtil.hash(hashCode, _ddmTemplateKey);
hashCode = HashUtil.hash(hashCode, _layoutSetId);
hashCode = HashUtil.hash(hashCode, _viewMode);
hashCode = HashUtil.hash(hashCode, _languageId);
hashCode = HashUtil.hash(hashCode, _page);
return HashUtil.hash(hashCode, _secure);
}
private JournalContentKey(
long groupId, String articleId, double version,
String ddmTemplateKey, long layoutSetId, String viewMode,
String languageId, int page, boolean secure) {
_groupId = groupId;
_articleId = articleId;
_version = version;
_ddmTemplateKey = ddmTemplateKey;
_layoutSetId = layoutSetId;
_viewMode = viewMode;
_languageId = languageId;
_page = page;
_secure = secure;
}
private static final long serialVersionUID = 1L;
private final String _articleId;
private final String _ddmTemplateKey;
private final long _groupId;
private final String _languageId;
private final long _layoutSetId;
private final int _page;
private final boolean _secure;
private final double _version;
private final String _viewMode;
}
\end{verbatim}
\texttt{JournalContentKey}s constructor populates fields that
collectively define unique keys for each piece of journal content.
Note a cache key's characteristics:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
A key instance's field values relate to the cached data and
distinguish it from other data instances.
\item
A key follows \texttt{Serializable} class best practices.
\begin{itemize}
\tightlist
\item
Overrides \texttt{Object}'s \texttt{equals} and \texttt{hashcode}
methods.
\item
Includes a private static final long \texttt{serialVersionUID}
field. It is to be incremented when a new version of the class is
incompatible with previous versions.
\end{itemize}
\end{enumerate}
Your cache key class is ready for caching data values.
\section{Step 3: Implement Cache
Logic}\label{step-3-implement-cache-logic}
When your application creates or requests the data type you're caching,
you must handle getting existing data from cache and putting new/updated
data into the cache. Liferay DXP's caching classes are easy to inject
into a
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{Declarative
Services (DS) Component}, but you can access them using
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{\texttt{ServiceTracker}}s
too. These steps use fictitious key and value classes: \texttt{SomeKey}
and \texttt{SomeValue}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Name your cache. Cache names are arbitrary, but they must be unique in
the cache pool, and typically identify the data type being cached.
\end{enumerate}
\begin{verbatim}
protected static final String CACHE_NAME = SomeValue.class.getName();
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
Access the VM pool you're using.
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/MultiVMPool.html}{\texttt{MultiVMPool}}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/SingleVMPool.html}{\texttt{SingleVMPool}}
are Declarative Service (DS) components. To access a pool from a DS
component, apply the
\href{https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Reference.html}{\texttt{@Reference}}
annotation to a pool field (see below). Otherwise, use a
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{\texttt{ServiceTracker}}
to access the pool.
\end{enumerate}
\begin{verbatim}
@Reference
private MultiVMPool _multiVMPool;
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
Declare a private static
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/PortalCache.html}{\texttt{PortalCache}}
instance.
\end{enumerate}
\begin{verbatim}
private static PortalCache _portalCache;
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
Initialize your \texttt{PortalCache} when your class is being
activated or initialized. If you're using a DS component, initialize
the cache in your component's activation method (annotated with
\href{https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Activate.html}{\texttt{@Activate}}).
Get the cache from your VM pool using your cache name. For example,
this DS component's activation method gets a cache from the multi-VM
pool.
\end{enumerate}
\begin{verbatim}
@Activate
public void activate() {
_portalCache =
(PortalCache)
_multiVMPool.getPortalCache(CACHE_NAME);
...
}
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
Similarly, remove your cache when your class instance is deactivated
or destroyed. If you're using a DS component, remove the cache in your
deactivation method (annotated with
\href{https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Deactivate.html}{\texttt{@Deactivate}}).
Use the VM pool to remove the cache.
\end{enumerate}
\begin{verbatim}
@Deactivate
public void deactivate() {
_multiVMPool.removePortalCache(CACHE_NAME);
}
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
In your code that uses the cached data, implement your caching logic.
Here's some example code:
\end{enumerate}
\begin{verbatim}
SomeKey key = new SomeKey(...);
SomeValue value = _portalCache.get(
key);
if (value == null) {
value = createSomeValue(...);
_portalCache.put(key, value);
}
// continue using the data
...
\end{verbatim}
The code above constructs a key based on the data being used. Then, the
key is used to check the \texttt{PortalCache} for the data. If the cache
doesn't have data associated with the key, data is created and put it
into the cache. The code continues using the cached data. Use similar
logic for the data you are caching.
Configuring the cache and deploying your project is next.
\section{Step 4: Configure the Cache}\label{step-4-configure-the-cache}
It's time to specify your Ehcache configuration.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Depending on the VM pool you're using, start your XML file in one of
the following ways.
\end{enumerate}
Multi VM file:
\begin{verbatim}
\end{verbatim}
Single VM file:
\begin{verbatim}
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Add a \texttt{\textless{}cache\textgreater{}} element for the cache
you're creating. Although the cache name is arbitrary, using a
name-spaced name such as a fully qualified class name is a best
practice.
Configure your \texttt{\textless{}cache\textgreater{}} element to fit
your caching requirements. The
\href{http://www.ehcache.org/ehcache.xsd}{ehcache.xsd} and
\href{http://www.ehcache.org/documentation/2.8/configuration/index.html}{Ehcache
documentation} describe the \texttt{\textless{}cache\textgreater{}}
attributes.
For example, the Liferay Web Experience suite's
\texttt{com.liferay.journal.service} module uses this
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/journal/journal-service/src/main/resources/META-INF/module-multi-vm.xml}{\texttt{module-multi-vm.xml}}
file to configure its cache named
\texttt{com.liferay.journal.util.JournalContent}.
\end{enumerate}
\begin{verbatim}
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
Deploy your project.
\end{enumerate}
Congratulations! Your data cache is in effect.
\section{Related Topics}\label{related-topics-14}
\href{/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache}{Overriding
Cache}
\chapter{Collaboration}\label{collaboration}
Underlying the
\href{/docs/7-2/user/-/knowledge_base/u/collaboration}{collaboration
suite} is a set of powerful APIs that add collaboration features to your
apps. For example, if your app contains a custom content type, you can
use the collaboration suite's social API to enable comments and ratings
for that content. You can also integrate your app with the Documents and
Media Library, and much more.
Here are a few of the things you can do with the collaboration suite's
APIs.
\chapter{Item Selector}\label{item-selector}
An \emph{Item Selector} is a UI component for selecting entities in a
user-friendly manner. Many Liferay apps use Item Selectors to select
items such as images, videos, audio files, documents, and pages. For
example, the Documents and Media Item Selector selects files.
\begin{figure}
\centering
\includegraphics{./images/item-selector-dialog-02.png}
\caption{Item Selectors select different kinds of entities.}
\end{figure}
The Item Selector API provides a framework for you to use, extend, and
create Item Selectors in your apps.
Here are some use cases for the Item Selector API:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Selecting entities with an Item Selector.
\item
Configuring an Item Selector to select your app's custom entity.
\item
Adding a new \emph{selection view} to customize the selection
experience.
\end{enumerate}
\chapter{Adaptive Media}\label{adaptive-media}
The
\href{/docs/7-2/user/-/knowledge_base/u/adapting-your-media-across-multiple-devices}{Adaptive
Media} app tailors the size and quality of images to the device
displaying them. For example, you can configure Adaptive Media to send
large, high-resolution images only to devices that can display them.
Other devices get images that consume less bandwidth and processing
power.
By default, Adaptive Media integrates with Documents and Media, Blogs,
and Web Content. You can also integrate it with your apps. Adaptive
Media contains a taglib that displays the adapted image matching the
file version you supply. You can also use Adaptive Media's finder API if
you need to get adapted images that match other criteria (e.g., a
specific resolution, a range of attributes, etc.). You can even
customize the image scaling that Adaptive Media uses to produce adapted
images.
\chapter{Social API}\label{social-api}
Users interact with content via Liferay DXP's social features. For
example, users can provide feedback on content, share that content with
others, subscribe to receive notifications, and more. Use the social API
to enable such functionality in your apps.
Here's an example of some functionality you can add to your apps via the
social API:
\textbf{Social Bookmarks:} Share content on social media. You can also
create new social bookmarks if one doesn't exist for your social network
of choice.
\textbf{Comments:} Comment on content.
\textbf{Ratings:} Rate content. Administrators can also change the
rating type (e.g., likes, stars, thumbs, etc.).
\textbf{Flags:} Flag inappropriate content.
\chapter{Documents and Media API}\label{documents-and-media-api}
Users can use, manage, and share files in the Documents and Media
Library. For example, users can embed files in content, organize them in
folders, edit and collaborate on them with other users, and more. See
the
\href{/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media}{user
guide} for more information on the Documents and Media Library's
features.
A powerful API underlies the Documents and Media Library's
functionality. You can leverage this API in your apps. For example, you
could create an app that uploads files to the Documents and Media
Library. Your app could even update, delete, and copy files.
Here's an example of some things you can do with the Documents and Media
API:
\begin{itemize}
\tightlist
\item
Create files, folders, and shortcuts.
\item
Delete entities.
\item
Update entities.
\item
Check out files for editing, and check them back in.
\item
Copy and move entities.
\item
Get entities.
\end{itemize}
\chapter{Item Selector}\label{item-selector-1}
An \emph{Item Selector} is a UI component for selecting entities in a
user-friendly manner.
Here's what you'll learn to do with Item Selectors:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select Entities.
\item
Create Custom Item Selector Criteria.
\item
Create Custom Item Selector Views.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/item-selector-dialog-02.png}
\caption{Item Selectors select entities.}
\end{figure}
\chapter{Understanding the Item Selector API's
Components}\label{understanding-the-item-selector-apis-components}
Before working with the Item Selector API, you should learn about its
components. You'll work with these components as you leverage the API in
your apps:
\textbf{Selection View:} A class that shows entities of particular types
from different sources. For example, an Item Selector configured to show
images might show selection views from Documents and Media, a
third-party image provider, or a drag-and-drop UI. Selection views are
the framework's key components.
\textbf{Markup:} A markup file that renders the selection view. You can
choose from JSP, FreeMarker, or even pure HTML and JavaScript.
\textbf{Return Type:} A class that represents the data type that entity
selections return. For example, if users select images and you want to
return the selected image's URL, then you need a URL return type. Each
return type class must implement
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorReturnType.html}{\texttt{ItemSelectorReturnType}}.
Such classes are named after the return type's data and suffixed with
\texttt{ItemSelectorReturnType}. For example, the URL return type class
is \texttt{URLItemSelectorReturnType}. The return type class is an API
that connects the return type to the Item Selector's views. The Item
Selector uses the return type class, which is empty and returns no
information, as an identifier. The view ensures that the proper
information is returned. If you create your own return type, you should
specify its data type and format in Javadoc.
\textbf{Criterion:} A class that represents the selected entity. For
example, if users select images, you need an image criterion class. Each
criterion class must implement
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorCriterion.html}{\texttt{ItemSelectorCriterion}}.
Such classes are named for the entity they represent and suffixed with
\texttt{ItemSelectorCriterion}. For example, the criterion class for
images is \texttt{ImageItemSelectorCriterion}. If you create your own
criterion class, extend
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterion.html}{\texttt{BaseItemSelectorCriterion}}.
This base class implements \texttt{ItemSelectorCriterion} and provides
methods that handle the Item Selector's return types. Your criterion
class can therefore be empty, unless you also want to use it to pass
information to the view.
Note that criterion and return types together form an Item Selector's
\emph{criteria}. The Item Selector uses its criteria to decide which
selection views to show.
\noindent\hrulefill
\textbf{Note:} For a list of the criterion classes and return types that
Liferay DXP provides, see
\href{/docs/7-2/reference/-/knowledge_base/r/item-selector-criterion-and-return-types}{Item
Selector Criterion and Return Types}.
\noindent\hrulefill
\textbf{Criterion Handler:} A class that gets the appropriate selection
view. Each criterion requires a criterion handler. Criterion handler
classes extend
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterionHandler.html}{\texttt{BaseItemSelectorCriterionHandler}}
with the criterion's entity as a type argument. Criterion handler
classes are named after the criterion's entity and suffixed by
\texttt{ItemSelectorCriterionHandler}. For example, the image criterion
handler class is \texttt{ImageItemSelectorCriterionHandler} and extends
\texttt{BaseItemSelectorCriterionHandler\textless{}ImageItemSelectorCriterion\textgreater{}}.
\begin{figure}
\centering
\includegraphics{./images/item-selector-architecture.png}
\caption{Item Selector views (selection views) are determined by the
return type and criterion, and rendered by the markup.}
\end{figure}
\chapter{Getting an Item Selector}\label{getting-an-item-selector}
To use an Item Selector with your criteria, you must get that Item
Selector's URL. The URL is needed to open the Item Selector dialog in
your UI. To get this URL, you must get an \texttt{ItemSelector}
reference and call its
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelector.html\#getItemSelectorURL-com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory-java.lang.String-com.liferay.item.selector.ItemSelectorCriterion...-}{\texttt{getItemSelectorURL}}
method with the following parameters:
\texttt{RequestBackedPortletURLFactory}: A factory that creates portlet
URLs.
\texttt{ItemSelectedEventName}: A unique, arbitrary JavaScript event
name that the Item Selector triggers when the entity is selected.
\texttt{ItemSelectorCriterion}: The criterion (or an array of criterion
objects) that specifies the type of entities to make available in the
Item Selector.
Keep these points in mind when getting an Item Selector's URL:
\begin{itemize}
\item
You can invoke the URL object's \texttt{toString} method to get its
value.
\item
You can configure an Item Selector to use any number of criterion. The
criterion can use any number of return types.
\item
The order of the Item Selector's criteria determines the selection
view order. For example, if you pass the Item Selector an
\texttt{ImageItemSelectorCriterion} followed by a
\texttt{VideoItemSelectorCriterion}, the Item Selector displays the
image selection views first.
\item
The return type order is also significant. A view uses the first
return type it supports from each criterion's return type list.
\end{itemize}
\chapter{Understanding Custom Selection
Views}\label{understanding-custom-selection-views}
The default selection views may provide everything you need for your
app. Custom selection views are required, however, for certain
situations. For example, you must create a custom selection view for
your users to select images from an external image provider.
The selected entity type determines the view the Item Selector presents.
The Item Selector can also render multiple views for the same entity
type. For example, several selection views are available for images.
Each selection view is a tab in the UI that corresponds to the image's
location. An \texttt{*ItemSelectorCriterion} class represents each
selection view.
\begin{figure}
\centering
\includegraphics{./images/item-selector-tabs.png}
\caption{An entity type can have multiple selection views.}
\end{figure}
\section{The Selection View's Class}\label{the-selection-views-class}
The criterion and return types determine the selection view's class.
This class is an \texttt{ItemSelectorView} component class that
implements
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html}{\texttt{ItemSelectorView}}
parameterized with the view's criterion. Remember these things when
creating this class:
\begin{itemize}
\item
Configure the title by implementing the
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html\#getTitle-java.util.Locale-}{\texttt{getTitle}}
method to return the localized title of the tab to display in the Item
Selector dialog.
\item
Configure the search options by implementing the
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html\#isShowSearch--}{\texttt{isShowSearch()}}
method to return whether your view should show the search field. To
implement search, this method must return \texttt{true}. The
\texttt{renderHTML} method indicates whether a user performed a search
based on the value of the \texttt{search} parameter. You can get the
user's search keywords as follows:
\begin{verbatim}
String keywords = ParamUtil.getString(request, "keywords");
\end{verbatim}
\item
Make your view visible by implementing the
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html\#isVisible-com.liferay.portal.kernel.theme.ThemeDisplay-}{\texttt{isVisible()}}
method to return \texttt{true}. Note that you can use this method to
add conditional logic to disable the view.
\end{itemize}
\chapter{Selecting Entities with an Item
Selector}\label{selecting-entities-with-an-item-selector}
The steps here show you how to get and use an Item Selector to select
entities in your app. For an explanation of the Item Selector API and
more information on these steps, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item
Selector introduction}.
\section{Get an Item Selector}\label{get-an-item-selector}
First, you must get an Item Selector for your use case. Follow these
steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Determine the criterion and return types for the Item Selector. The
criterion corresponds to the selected entity type, and the return
types correspond to the data you expect to receive from those
selections. For a list of the criterion and return types that Liferay
DXP provides, see
\href{/docs/7-2/reference/-/knowledge_base/r/item-selector-criterion-and-return-types}{Item
Selector Criterion and Return Types}. For example, if you need an Item
Selector that selects images and returns their URLs, use
\texttt{ImageItemSelectorCriterion} and
\texttt{URLItemSelectorReturnType}. You can
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-criterion-and-return-types}{create}
criterion and/or return types if there aren't existing ones for your
use case.
\item
Use Declarative Services to get an \texttt{ItemSelector} OSGi Service
Component:
\begin{verbatim}
import com.liferay.item.selector.ItemSelector;
import org.osgi.service.component.annotations.Reference;
...
@Reference
private ItemSelector _itemSelector
\end{verbatim}
The component annotations are available in the module
\href{http://mvnrepository.com/artifact/org.osgi/org.osgi.service.component.annotations}{\texttt{org.osgi.service.component.annotations}}.
\item
Create the factory you'll use to create the Item Selector's URL. To do
this, invoke the \texttt{RequestBackedPortletURLFactoryUtil.create}
method with the current request object. The request can be an
\texttt{HttpServletRequest} or \texttt{PortletRequest}:
\begin{verbatim}
RequestBackedPortletURLFactory requestBackedPortletURLFactory =
RequestBackedPortletURLFactoryUtil.create(request);
\end{verbatim}
\item
Create a list of return types expected for the entity. For example,
the return types list here consists of
\texttt{URLItemSelectorReturnType}:
\begin{verbatim}
List desiredItemSelectorReturnTypes =
new ArrayList<>();
desiredItemSelectorReturnTypes.add(new URLItemSelectorReturnType());
\end{verbatim}
\item
Create an object for the criterion. This example creates a new
\texttt{ImageItemSelectorCriterion}:
\begin{verbatim}
ImageItemSelectorCriterion imageItemSelectorCriterion =
new ImageItemSelectorCriterion();
\end{verbatim}
\item
Use the criterion's \texttt{setDesiredItemSelectorReturnTypes} method
to set the return types list to the criterion:
\begin{verbatim}
imageItemSelectorCriterion.setDesiredItemSelectorReturnTypes(
desiredItemSelectorReturnTypes);
\end{verbatim}
\item
Call the Item Selector's \texttt{getItemSelectorURL} method to get an
Item Selector URL for the criterion. The method requires the URL
factory, an arbitrary event name, and a series of criterion instances
(one, in this case):
\begin{verbatim}
PortletURL itemSelectorURL = _itemSelector.getItemSelectorURL(
requestBackedPortletURLFactory, "sampleTestSelectItem",
imageItemSelectorCriterion);
\end{verbatim}
\item
Add the \texttt{itemSelectorURL} to the request to be able to retrieve
it from the JSP:
\texttt{\textless{}code/\textgreater{}request.setAttribute("itemSelectorURL",\ itemSelectorURL.toString())\textless{}/code\textgreater{}"}
\end{enumerate}
\section{Using the Item Selector
Dialog}\label{using-the-item-selector-dialog}
To open the Item Selector in your UI, you must use the JavaScript
component \texttt{LiferayItemSelectorDialog} from
\href{http://alloyui.com}{AlloyUI's}
\texttt{liferay-item-selector-dialog} module. The component listens for
the item selected event that you specified for the Item Selector URL.
The event returns the selected element's information according to its
return type.
Follow these steps to use the Item Selector's dialog in a JSP:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Declare the AUI tag library:
\begin{verbatim}
<%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %>
\end{verbatim}
\item
Define the UI element you'll use to open the Item Selector dialog. For
example, this creates a \emph{Choose} button with the ID
\texttt{chooseImage}:
\begin{verbatim}
\end{verbatim}
\item
Get the Item Selector's URL:
\begin{verbatim}
<%
String itemSelectorURL = GetterUtil.getString(request.getAttribute("itemSelectorURL"));
%>
\end{verbatim}
\item
Add the \texttt{\textless{}aui:script\textgreater{}} tag and set it to
use the \texttt{liferay-item-selector-dialog} module:
\begin{verbatim}
\end{verbatim}
\item
Inside the \texttt{\textless{}aui:script\textgreater{}} tag, attach an
event handler to the UI element you created in step two. For example,
this attaches a click event and a function to the \emph{Choose}
button:
\begin{verbatim}
$('# chooseImage').on(
'click',
function(event) {
}
);
\end{verbatim}
Inside the function, you must create a new instance of the
\texttt{LiferayItemSelectorDialog} AlloyUI component and configure it
to use the Item Selector. The next steps walk you through this.
\item
Create the function's logic. First, create a new instance of the
Liferay Item Selector dialog:
\begin{verbatim}
var itemSelectorDialog = new A.LiferayItemSelectorDialog(
{
...
}
);
\end{verbatim}
\item
Inside the braces of the \texttt{LiferayItemSelectorDialog}
constructor, first set set the \texttt{eventName} attribute. This
makes the dialog listen for the item selected event. The event name is
the Item Selector's event name that you specified in your Java code
(the code that gets the Item Selector URL):
\begin{verbatim}
eventName: 'ItemSelectedEventName',
\end{verbatim}
\item
Immediately after the \texttt{eventName} setting, set the \texttt{on}
attribute to implement a function that operates on the selected item
change. For example, this function sets its variables for the newly
selected item. The information available to parse depends on the
return type(s). As the comment below indicates, you must add the logic
for using the selected element:
\begin{verbatim}
on: {
selectedItemChange: function(event) {
var selectedItem = event.newVal;
if (selectedItem) {
var itemValue = JSON.parse(
selectedItem.value
);
itemSrc = itemValue.url;
}
}
},
\end{verbatim}
\item
Immediately after the \texttt{on} setting, set the \texttt{title}
attribute to the dialog's title:
\begin{verbatim}
title: ' ',
\end{verbatim}
\item
Immediately after the \texttt{title} setting, set the \texttt{url}
attribute to the previously retrieved Item Selector URL. This
concludes the attribute settings inside the
\texttt{LiferayItemSelectorDialog} constructor:
\begin{verbatim}
url: '<%= itemSelectorURL.toString() %>'
\end{verbatim}
\item
To conclude the logic of the function from step four, open the Item
Selector dialog by calling its \texttt{open} method:
\begin{verbatim}
itemSelectorDialog.open();
\end{verbatim}
\end{enumerate}
When the user clicks the \emph{Choose} button, a new dialog opens,
rendering the Item Selector with the views that support the criterion
and return type(s).
Here's the complete example code for these steps:
\begin{verbatim}
<%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %>
<%
String itemSelectorURL = GetterUtil.getString(request.getAttribute("itemSelectorURL"));
%>
$('# chooseImage').on(
'click',
function(event) {
var itemSelectorDialog = new A.LiferayItemSelectorDialog(
{
eventName: 'ItemSelectedEventName',
on: {
selectedItemChange: function(event) {
var selectedItem = event.newVal;
if (selectedItem) {
var itemValue = JSON.parse(
selectedItem.value
);
itemSrc = itemValue.url;
}
}
},
title: ' ',
url: '<%= itemSelectorURL.toString() %>'
}
);
itemSelectorDialog.open();
}
);
\end{verbatim}
\section{Related Topics}\label{related-topics-15}
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item
Selector}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-criterion-and-return-types}{Creating
Custom Criterion and Return Types}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-item-selector-views}{Creating
Custom Item Selector Views}
\chapter{Creating Custom Criterion and Return
Types}\label{creating-custom-criterion-and-return-types}
If an existing criterion or return type doesn't fit your use case, you
can create them. The steps here show you how. For more detailed
information on Item Selector criterion and return types, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item
Selector introduction}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create your criterion class by extending
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterion.html}{\texttt{BaseItemSelectorCriterion}}.
Name the class after the entity it represents and suffix it with
\texttt{ItemSelectorCriterion}. You can use the class to pass
information to the view if needed. Otherwise, your criterion class can
be empty. If you pass information to the view, any fields in your
criterion class should be serializable and you should expose an empty
public constructor.
For example,
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/journal/item/selector/criterion/JournalItemSelectorCriterion.html}{\texttt{JournalItemSelectorCriterion}}
is the criterion class for \texttt{Journal} entities (Web Content) and
passes primary key information to the view:
\begin{verbatim}
public class JournalItemSelectorCriterion extends BaseItemSelectorCriterion {
public JournalItemSelectorCriterion() {
}
public JournalItemSelectorCriterion(long resourcePrimKey) {
_resourcePrimKey = resourcePrimKey;
}
public long getResourcePrimKey() {
return _resourcePrimKey;
}
public void setResourcePrimKey(long resourcePrimKey) {
_resourcePrimKey = resourcePrimKey;
}
private long _resourcePrimKey;
}
\end{verbatim}
\item
Create a criterion handler by creating an OSGi component class that
implements
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterionHandler.html}{\texttt{BaseItemSelectorCriterionHandler}}.
This example creates a criterion handler for the
\texttt{TaskItemSelectorCriterion} class. The \texttt{@Activate} and
\texttt{@Override} tokens are required to activate this OSGi
component:
\begin{verbatim}
@Component(service = ItemSelectorCriterionHandler.class)
public class TaskItemSelectorCriterionHandler extends
BaseItemSelectorCriterionHandler {
public Class getItemSelectorCriterionClass() {
return TasksItemSelectorCriterionHandler.class;
}
@Activate
@Override
protected void activate(BundleContext bundleContext) {
super.activate(bundleContext);
}
}
\end{verbatim}
\item
If you need a new return type, create it by implementing
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorReturnType.html}{\texttt{ItemSelectorReturnType}}.
Name your return type class after the return type's data and suffix it
with \texttt{ItemSelectorReturnType}. Specify the data and its format
in Javadoc. Return type classes need no content. For example, here's a
return type for a task:
\begin{verbatim}
/**
* This return type should return the task ID and the user who
* created the task as a string.
*
* @author Joe Bloggs
*/
public class TaskItemSelectorReturnType implements ItemSelectorReturnType{
}
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-16}
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item
Selector}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-item-selector-views}{Creating
Custom Item Selector Views}
\href{/docs/7-2/frameworks/-/knowledge_base/f/selecting-entities-with-an-item-selector}{Selecting
Entities with an Item Selector}
\chapter{Creating Custom Item Selector
Views}\label{creating-custom-item-selector-views}
You can create your own selection view if an Item Selector doesn't
contain the one you need. The steps here show you how. For more
information on custom selection views and the Item Selector API, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item
Selector introduction}.
\section{Configuring Your Selection View's OSGi
Module}\label{configuring-your-selection-views-osgi-module}
First, you must configure your selection view's OSGi module:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add these dependencies to your module's \texttt{build.gradle}:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay", name: "com.liferay.item.selector.api", version: "2.0.0"
compileOnly group: "com.liferay", name: "com.liferay.item.selector.criteria.api", version: "2.0.0"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.impl", version: "2.0.0"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0"
compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
}
\end{verbatim}
\item
Add your module's information to the \texttt{bnd.bnd} file. For
example, this configuration adds the information for a module called
\texttt{My\ Custom\ View}:
\begin{verbatim}
Bundle-Name: My Custom View
Bundle-SymbolicName: com.liferay.docs.my.custom.view
Bundle-Version: 1.0.0
\end{verbatim}
\item
Add a \texttt{Web-ContextPath} to your \texttt{bnd.bnd} to point to
your module's resources:
\begin{verbatim}
Include-Resource:\
META-INF/resources=src/main/resources/META-INF/resources
Web-ContextPath: /my-custom-view
\end{verbatim}
If you don't have a \texttt{Web-ContextPath}, your module won't know
where your resources are. The \texttt{Include-Resource} header points
to the relative path for the module's resources.
\end{enumerate}
\section{Implementing Your Selection View's
Class}\label{implementing-your-selection-views-class}
Follow these steps to implement your selection view's class:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an \texttt{ItemSelectorView} component class that implements
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html}{\texttt{ItemSelectorView}}
with the criterion as a type argument. In the \texttt{@Component}
annotation, set the \texttt{item.selector.view.order} property to the
order you want the view to appear in when displayed alongside other
selector views (lower values get higher priority).
This example selector view class is for images, so it implements
\texttt{ItemSelectorView} with
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/criteria/image/criterion/ImageItemSelectorCriterion.html}{\texttt{ImageItemSelectorCriterion}}
as a type argument. The \texttt{@Component} annotation sets the
\texttt{item.selector.view.order} property to \texttt{200} and
registers the class as an \texttt{ItemSelectorView} service:
\begin{verbatim}
@Component(
property = {"item.selector.view.order:Integer=200"},
service = ItemSelectorView.class
)
public class SampleItemSelectorView
implements ItemSelectorView {...
\end{verbatim}
\item
Create getter methods for the criterion class, servlet context, and
return types. Do this by implementing the methods
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html\#getItemSelectorCriterionClass--}{\texttt{getItemSelectorCriterionClass()}},
\texttt{getServletContext()}, and
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html\#getSupportedItemSelectorReturnTypes--}{\texttt{getSupportedItemSelectorReturnTypes()}},
respectively:
\begin{verbatim}
@Override
public Class getItemSelectorCriterionClass()
{
return ImageItemSelectorCriterion.class;
}
@Override
public ServletContext getServletContext() {
return _servletContext;
}
@Override
public List getSupportedItemSelectorReturnTypes() {
return _supportedItemSelectorReturnTypes;
}
\end{verbatim}
\item
Configure the selection view's title, search options, and visibility
settings. Here's an example configuration for the
\texttt{Sample\ Selector} selection view:
\begin{verbatim}
@Override
public String getTitle(Locale locale) {
return "Sample Selector";
}
@Override
public boolean isShowSearch() {
return false;
}
@Override
public boolean isVisible(ThemeDisplay themeDisplay) {
return true;
}
\end{verbatim}
See
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector\#the-selection-views-class}{The
Selection View's Class} for more information on these methods.
\item
Implement the
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html\#renderHTML-javax.servlet.ServletRequest-javax.servlet.ServletResponse-T-javax.portlet.PortletURL-java.lang.String-boolean-}{\texttt{renderHTML}}
method to set your view's render settings and render its markup.
Here's an example implementation of a \texttt{renderHTML} method that
points to a JSP file (\texttt{sample.jsp}) to render the view. Note
that \texttt{itemSelectedEventName} is passed as a request attribute
so it can be used in the view markup. The view markup is specified via
the \texttt{ServletContext} method \texttt{getRequestDispatcher}.
Although this example uses a JSP, you can render the markup in another
language such as FreeMarker.
\begin{verbatim}
@Override
public void renderHTML(
ServletRequest request, ServletResponse response,
ImageItemSelectorCriterion itemSelectorCriterion,
PortletURL portletURL, String itemSelectedEventName,
boolean search
)
throws IOException, ServletException {
request.setAttribute(_ITEM_SELECTED_EVENT_NAME,
itemSelectedEventName);
ServletContext servletContext = getServletContext();
RequestDispatcher requestDispatcher =
servletContext.getRequestDispatcher("/sample.jsp");
requestDispatcher.include(request, response);
}
\end{verbatim}
\item
Use the \texttt{@Reference} annotation to reference your module's
class for the \texttt{setServletContext} method. In the annotation,
use the \texttt{target} parameter to specify the available services
for the servlet context. This example uses the
\texttt{osgi.web.symbolicname} property to specify the
\texttt{com.liferay.selector.sample.web} class as the default value.
You should also use the \texttt{unbind\ =\ \_} parameter to specify
that there's no unbind method for this module. In the method body, set
the servlet context variable:
\begin{verbatim}
@Reference(
target =
"(osgi.web.symbolicname=com.liferay.item.selector.sample.web)",
unbind = "-"
)
public void setServletContext(ServletContext servletContext) {
_servletContext = servletContext;
}
\end{verbatim}
\item
Define the \texttt{\_supportedItemSelectorReturnTypes} list with the
return types that this view supports (you referenced this list in step
two). This example adds
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/criteria/URLItemSelectorReturnType.html}{\texttt{URLItemSelectorReturnType}}
and
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/item/selector/criteria/FileEntryItemSelectorReturnType.html}{\texttt{FileEntryItemSelectorReturnType}}
to the list of supported return types (you can use more if needed).
More return types means that the view is more reusable. Also note that
this example defines its servlet context variable at the bottom of the
file:
\begin{verbatim}
private static final List
_supportedItemSelectorReturnTypes =
Collections.unmodifiableList(
ListUtil.fromArray(
new ItemSelectorReturnType[] {
new FileEntryItemSelectorReturnType(),
new URLItemSelectorReturnType()
}));
private ServletContext _servletContext;
\end{verbatim}
\end{enumerate}
For a real-world example of a view class, see
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/site-navigation/site-navigation-item-selector-web/src/main/java/com/liferay/site/navigation/item/selector/web/internal/SiteNavigationMenuItemItemSelectorView.java}{\texttt{SiteNavigationMenuItemItemSelectorView}}.
\section{Writing Your View Markup}\label{writing-your-view-markup}
You can write your view markup however you wish---there's no typical or
average case. You can write it with taglibs, AUI components, or even
pure HTML and JavaScript. The markup must do two key things:
\begin{itemize}
\tightlist
\item
Render the entities for the user to select.
\item
When an entity is selected, pass the return type information via a
JavaScript event.
\end{itemize}
The example view class in the previous section passes the JavaScript
event name as a request attribute in the \texttt{renderHTML} method. You
can therefore use this event name in the markup:
\begin{verbatim}
Liferay.fire(
`<%= {ITEM_SELECTED_EVENT_NAME} %>',
{
data:{
the-data-your-client-needs-according-to-the-return-type
}
}
);
\end{verbatim}
For a complete, real-world example, see
\href{https://github.com/liferay/liferay-portal/blob/7.0.x/modules/apps/web-experience/layout/layout-item-selector-web/src/main/resources/META-INF/resources/layouts.jsp}{\texttt{layouts.jsp}}
for the module
\href{https://github.com/liferay/liferay-portal/tree/7.0.x/modules/apps/web-experience/layout/layout-item-selector-web}{\texttt{com.liferay.layout.item.selector.web}}.
Even though this example is for a previous version of Liferay DXP, it
still applies to 7.0. Here's a walkthrough of this \texttt{layouts.jsp}
file:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
First, some variables are defined. Note that
\texttt{LayoutItemSelectorViewDisplayContext} is an optional class
that contains additional information about the criteria and view:
\begin{verbatim}
<%
LayoutItemSelectorViewDisplayContext layoutItemSelectorViewDisplayContext =
(LayoutItemSelectorViewDisplayContext)request.getAttribute(
BaseLayoutsItemSelectorView.LAYOUT_ITEM_SELECTOR_VIEW_DISPLAY_CONTEXT);
LayoutItemSelectorCriterion layoutItemSelectorCriterion =
layoutItemSelectorViewDisplayContext.getLayoutItemSelectorCriterion();
Portlet portlet = PortletLocalServiceUtil.getPortletById(company.getCompanyId(),
portletDisplay.getId());
%>
\end{verbatim}
\item
This snippet imports a CSS file for styling and places it in the
\texttt{\textless{}head\textgreater{}} of the page:
\begin{verbatim}
" rel="stylesheet" type="text/css" />
\end{verbatim}
You can learn more about using the \texttt{liferay-util} taglibs in
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-liferay-util-taglib}{Using
the Liferay Util Taglib}.
\item
This snippet creates the UI to display the layout entities. It uses
the
\href{https://docs.liferay.com/dxp/apps/layout/latest/taglibdocs/liferay-layout/layouts-tree.html}{\texttt{liferay-layout:layouts-tree}}
taglib along with the \href{https://lexicondesign.io/}{Lexicon} design
language to create
\href{https://clayui.com/docs/components/cards.html}{cards}:
\begin{verbatim}
\end{verbatim}
This renders the following UI:
\begin{figure}
\centering
\includegraphics{./images/layouts-item-selector-view.png}
\caption{The Layouts Item Selector view uses Lexicon and Liferay
Layout taglibs to create the UI.}
\end{figure}
\item
This portion of the \texttt{aui:script} returns the path for the page:
\begin{verbatim}
var LString = A.Lang.String;
var getChosenPagePath = function(node) {
var buffer = [];
if (A.instanceOf(node, A.TreeNode)) {
var labelText = LString.escapeHTML(node.get('labelEl').text());
buffer.push(labelText);
node.eachParent(
function(treeNode) {
var labelEl = treeNode.get('labelEl');
if (labelEl) {
labelText = LString.escapeHTML(labelEl.text());
buffer.unshift(labelText);
}
}
);
}
return buffer.join(' > ');
};
\end{verbatim}
\item
The following snippet passes the return type data when the layout
(entity) is selected. Note the \texttt{url} and \texttt{uuid}
variables retrieve the URL or UUID for the layout:
\begin{verbatim}
var setSelectedPage = function(event) {
var disabled = true;
var messageText = '<%= UnicodeLanguageUtil.get(request, "there-is-no-selected-page") %>';
var lastSelectedNode = event.newVal;
var labelEl = lastSelectedNode.get('labelEl');
var link = labelEl.one('a');
var url = link.attr('data-url');
var uuid = link.attr('data-uuid');
var data = {};
if (link && url) {
disabled = false;
data.layoutpath = getChosenPagePath(lastSelectedNode);
\end{verbatim}
\item
This checks if the return type information is a URL or a UUID. It then
sets the value for the JSON object's \texttt{data} attribute
accordingly. The last line adds the \texttt{CKEditorFuncNum} for the
editor to the JSON object's \texttt{data} attribute:
\begin{verbatim}
data.value = url;
data.value = uuid;
}
data.ckeditorfuncnum: <%= layoutItemSelectorViewDisplayContext.getCkEditorFuncNum() %>;
\end{verbatim}
The \texttt{data-url} and \texttt{data-uuid} attributes are in the
HTML for the Layouts Item Selector. The HTML for an instance of the
Layouts Item Selector is shown here:
\begin{figure}
\centering
\includegraphics{./images/layouts-item-selector-html.png}
\caption{The URL and UUID can be seen in the \texttt{data-url} and
\texttt{data-uuid} attributes of the Layout Item Selector's HTML.}
\end{figure}
\item
The JavaScript trigger event specified in the Item Selector return
type is fired, passing the data JSON object with the required return
type information:
\begin{verbatim}
Liferay.Util.getOpener().Liferay.fire(
'<%= layoutItemSelectorViewDisplayContext.getItemSelectedEventName() %>',
{
data: data
}
);
};
\end{verbatim}
\item
Finally, the layout is set to the selected page:
\begin{verbatim}
var container = A.one('# treeContainerOutput');
if (container) {
container.swallowEvent('click', true);
var tree = container.getData('tree-view');
tree.after('lastSelectedChange', setSelectedPage);
}
\end{verbatim}
\end{enumerate}
Your new selection view is automatically rendered by the Item Selector
in every app that uses the criterion and return types you defined,
without modifying anything in those apps.
\section{Related Topics}\label{related-topics-17}
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item
Selector}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-criterion-and-return-types}{Creating
Custom Criterion and Return Types}
\href{/docs/7-2/frameworks/-/knowledge_base/f/selecting-entities-with-an-item-selector}{Selecting
Entities with an Item Selector}
\chapter{Documents and Media API}\label{documents-and-media-api-1}
A powerful API underlies the
\href{/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media}{Documents
and Media library}. You can leverage this API in your own apps. For
example, you could create an app that lets users upload files to the
Documents and Media library. Your app could even let users update,
delete, and copy files.
Here, you'll learn how to use the Documents and Media library's API.
Note that this is a large API and it may seem daunting at first. To keep
backwards compatibility, the API has different entry points and multiple
methods or classes with similar functionality. Fortunately, you don't
need to learn all of them. The content here focuses on the API's most
useful classes and methods.
Also note that the Documents and Media app is itself a consumer of this
API---Liferay's developers used the API to implement the app's
functionality. Therefore, code from this app is used as an example of
how to use the API.
\chapter{Getting Started with the Documents and Media
API}\label{getting-started-with-the-documents-and-media-api}
Before you start using the Documents and Media API, you must learn these
things:
\hyperref[key-interfaces]{\textbf{Key Interfaces:}} The interfaces
you'll use most while using the API.
\hyperref[getting-a-service-reference]{\textbf{Getting a Service
Reference:}} A service reference is required for calling the API's
services.
\hyperref[specifying-repositories]{\textbf{Specifying Repositories:}}
How to specify which Documents and Media repository to work with.
\hyperref[specifying-folders]{\textbf{Specifying Folders:}} How to
specify which Documents and Media folder to work with.
\chapter{Key Interfaces}\label{key-interfaces}
The Documents and Media API contains several key interfaces:
\textbf{Documents and Media Services:} These interfaces expose all the
available Documents and Media functionality:
\begin{itemize}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppLocalService.html}{\texttt{DLAppLocalService}}:
The local service.
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}:
The remote service. This service wraps the local service methods in
permission checks.
Note that Liferay used
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} to create these services. Because the remote service contains
permission checks, it's a best practice to call it instead of the
local service. See below for instructions on getting a service
reference.
\end{itemize}
\textbf{Entity Interfaces:} These interfaces represent entities in the
Documents and Media library. Here are the primary ones you'll use:
\begin{itemize}
\tightlist
\item
\texttt{FileEntry}: Represents a file.
\item
\texttt{Folder}: Represents a folder.
\item
\texttt{FileShortcut}: Represents a shortcut to a file.
\end{itemize}
\chapter{Getting a Service Reference}\label{getting-a-service-reference}
Before you can do anything with the Documents and Media API, you must
get a service reference. If you're using OSGi modules, use the
\texttt{@Reference} annotation to
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{get
a service reference in an OSGi component via Declarative Services}. For
example, this code gets a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
If you're using a standard web module (WAR file), use a
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{Service
Tracker} to get a reference to the service instead.
Getting the reference this way ensures that you leverage OSGi's
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{dependency
management} features. If you must use the Documents and Media services
outside of an OSGi component (e.g., in a JSP), then you can use the
services' static \texttt{*Util} classes:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppServiceUtil.html}{\texttt{DLAppServiceUtil}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppLocalServiceUtil.html}{\texttt{DLAppLocalServiceUtil}}
\end{itemize}
\chapter{Specifying Repositories}\label{specifying-repositories}
Many methods in the Documents and Media API contain a
\texttt{repositoryId} parameter that identifies the Documents and Media
repository where the operation is performed. A Site (group) can have
multiple repositories, but only one can be accessed via the portal UI.
This is called the Site repository, which is effectively a Site's
default repository. To access this repository via the API, provide the
group ID as the \texttt{repositoryId}.
You can also get the \texttt{repositoryId} via file
(\texttt{FileEntry}), folder (\texttt{Folder}), and file shortcut
(\texttt{FileShortcut}) entities. Each of these entities has a
\texttt{getRepositoryId} method that gets its repository's ID. For
example, this code gets the repository ID of the \texttt{FileEntry}
object \texttt{fileEntry}:
\begin{verbatim}
long repositoryId = fileEntry.getRepositoryId();
\end{verbatim}
There may also be cases that require a \texttt{Repository} object. You
can get one by creating a \texttt{RepositoryProvider} reference and
passing the repository ID to its \texttt{getRepository} method:
\begin{verbatim}
@Reference
private RepositoryProvider repositoryProvider;
Repository repository = repositoryProvider.getRepository(repositoryId);
\end{verbatim}
Even if you only have an entity ID (e.g., a file or folder ID), you can
still use \texttt{RepositoryProvider} to get a \texttt{Repository}
object. To do so, call the \texttt{RepositoryProvider} method for the
entity type with the entity ID as its argument. For example, this code
gets a folder's \texttt{Repository} by calling the
\texttt{RepositoryProvider} method \texttt{getFolderRepository} with the
folder's ID:
\begin{verbatim}
Repository repository = repositoryProvider.getFolderRepository(folderId);
\end{verbatim}
See the \texttt{RepositoryProvider}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/RepositoryProvider.html}{Javadoc}
for a list of the methods for other entity types.
Note that there are ways to create repositories programmatically,
including repositories private to specific apps. For simplicity,
however, the examples here access the default site repository.
\chapter{Specifying Folders}\label{specifying-folders}
Many API methods require the ID of a folder that they perform operations
in or on. For example, such methods may contain parameters like
\texttt{folderId} or \texttt{parentFolderId}. Also note that you can use
the constant \texttt{DLFolderConstants.DEFAULT\_PARENT\_FOLDER\_ID} to
specify the root folder of your current repository.
\chapter{Creating Files, Folders, and
Shortcuts}\label{creating-files-folders-and-shortcuts}
A primary use case for the Docs \& Media API is to create files,
folders, and file shortcuts in the Documents and Media library.
If you've used other Liferay APIs, the Docs \& Media API follows the
same conventions. In general, methods that do similar things have
similar names. When you must create an entity (whatever it is), look for
methods that follow the pattern \texttt{add{[}ModelName{]}}, where
\texttt{{[}ModelName{]}} is the name of the entity's data model object.
As the
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{intro}
explains, you'll use
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
to access the API. This service object contains the methods for adding
these entities:
\begin{itemize}
\tightlist
\item
\hyperref[files]{Files}
\item
\hyperref[folders]{Folders}
\item
\hyperref[file-shortcuts]{File Shortcuts}
\end{itemize}
\chapter{Files}\label{files}
To create files (\texttt{FileEntry} entities) in the Documents and Media
library, you must use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
interface's \texttt{addFileEntry} methods. There are three such methods,
and they differ by the data type used to create the file. Click each
method to see a full description of the method and its parameters:
\begin{itemize}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#addFileEntry-long-long-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-byte:A-com.liferay.portal.kernel.service.ServiceContext-}{\texttt{addFileEntry(...,\ byte{[}{]}\ bytes,\ ...)}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#addFileEntry-long-long-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.io.File-com.liferay.portal.kernel.service.ServiceContext-}{\texttt{addFileEntry(...,\ File\ file,\ ...)}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#addFileEntry-long-long-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.io.InputStream-long-com.liferay.portal.kernel.service.ServiceContext-}{\texttt{addFileEntry(...,\ InputStream\ is,\ long\ size,\ ...)}}
\end{itemize}
Note that the following arguments are optional:
\texttt{sourceFileName}: This keeps track of the uploaded file. It
infers the content type if that file has an extension.
\texttt{mimeType}: Defaults to a binary stream. If omitted, Documents
and Media tries to infer the type from the file extension.
\texttt{description}: The file's description to display in the portal.
\texttt{changeLog}: Descriptions for file versions.
\texttt{is} and \texttt{size}: In the method that takes an
\texttt{InputStream}, you can use \texttt{null} for the \texttt{is}
parameter. If you do this, however, you must use \texttt{0} for the
\texttt{size} parameter.
For step-by-step instructions on creating files with
\texttt{addFileEntry}, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files}{Creating
Files}.
\chapter{Folders}\label{folders}
To create folders (\texttt{Folder} entities) in the Documents and Media
library, you must use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
interface's \texttt{addFolder} method:
\begin{verbatim}
addFolder(long repositoryId,
long parentFolderId,
String name,
String description,
ServiceContext serviceContext)
\end{verbatim}
See this method's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#addFolder-long-long-java.lang.String-java.lang.String-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}
for a description of the parameters. Note that the \texttt{description}
parameter is optional.
For step-by-step instructions on creating folders with
\texttt{addFolder}, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-folders}{Creating
Folders}.
\section{Folders and External
Repositories}\label{folders-and-external-repositories}
By creating a folder that acts as a proxy for an external repository
(e.g., SharePoint), you can effectively mount that repository inside a
Site's default repository. When users enter this special folder, they
see the external repository. These folders are called \emph{mount
points}. You can create one via the API by setting the Service Context's
\texttt{mountPoint} attribute to \texttt{true}, and then using that
Service Context in the \texttt{addFolder} method:
\begin{verbatim}
serviceContext.setAttribute("mountPoint", true);
\end{verbatim}
Note that the \texttt{repositoryId} of such a folder indicates the
external repository the folder points to---not the repository the folder
exists in. Also, mount point folders can only exist in the default Site
repository.
\chapter{File Shortcuts}\label{file-shortcuts}
To create file shortcuts (\texttt{FileShortcut} entities) in the
Documents and Media library, you must use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
interface's \texttt{addFileShortcut} method:
\begin{verbatim}
addFileShortcut(long repositoryId,
long folderId,
long toFileEntryId,
ServiceContext serviceContext)
\end{verbatim}
See this method's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#addFileShortcut-long-long-long-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}
for a description of the parameters. Note that all this method's
parameters are mandatory.
Keep these things in mind when creating shortcuts:
\begin{itemize}
\tightlist
\item
You can create a shortcut to a file in a different Site, if that file
and its resulting shortcut are in the same portal instance.
\item
You can't create folder shortcuts.
\item
Shortcuts can only exist in the default Site repository. If you try to
invoke \texttt{addFileShortcut} with an external repository's ID
(e.g., a SharePoint repository), the operation fails. Because not all
repositories have the same features, the Documents and Media API only
supports the common denominators for all repositories: files and
folders.
\end{itemize}
For step-by-step instructions on creating file shortcuts with
\texttt{addFileShortcut}, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts}{Creating
File Shortcuts}.
\chapter{Creating Files}\label{creating-files}
To create a file via the Documents and Media API, use one of the
overloaded \texttt{addFileEntry} methods in
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}.
The steps here show you how to do this, using the method that contains
\texttt{InputStream} as an example. For detailed information on this and
other \texttt{addFileEntry} methods, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts}{Creating
Files, Folders, and Shortcuts}. For general information on using the
API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to create a file via the Documents and Media API:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{addFileEntry} method's
arguments. Since it's common to create a file with data submitted by
the end user, you can extract the data from the request. This example
does so via
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upload/UploadPortletRequest.html}{\texttt{UploadPortletRequest}}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
long repositoryId = ParamUtil.getLong(uploadPortletRequest, "repositoryId");
long folderId = ParamUtil.getLong(uploadPortletRequest, "folderId");
String sourceFileName = uploadPortletRequest.getFileName("file");
String title = ParamUtil.getString(uploadPortletRequest, "title");
String description = ParamUtil.getString(uploadPortletRequest, "description");
String changeLog = ParamUtil.getString(uploadPortletRequest, "changeLog");
boolean majorVersion = ParamUtil.getBoolean(uploadPortletRequest, "majorVersion");
try (InputStream inputStream = uploadPortletRequest.getFileAsStream("file")) {
String contentType = uploadPortletRequest.getContentType("file");
long size = uploadPortletRequest.getSize("file");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFileEntry.class.getName(), uploadPortletRequest);
}
\end{verbatim}
\item
Call the service reference's \texttt{addFileEntry} method with the
data from the previous step. Note that this example does so inside the
previous step's try-with-resources statement:
\begin{verbatim}
try (InputStream inputStream = uploadPortletRequest.getFileAsStream("file")) {
...
FileEntry fileEntry = _dlAppService.addFileEntry(
repositoryId, folderId, sourceFileName, contentType, title,
description, changeLog, inputStream, size, serviceContext);
}
\end{verbatim}
The method returns a \texttt{FileEntry} object, which this example
sets to a variable for later use. Note, however, that you don't have
to do this.
\end{enumerate}
You can find the full code for this example in the
\texttt{updateFileEntry} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileEntryMVCActionCommand.java}{\texttt{EditFileEntryMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileEntry} actions that the Documents and Media app
supports. Also note that this \texttt{updateFileEntry} method, as well
as the rest of \texttt{EditFileEntryMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-18}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-files}{Updating
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-files}{Deleting
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-folders}{Creating
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts}{Creating
File Shortcuts}
\chapter{Creating Folders}\label{creating-folders}
To create folders (\texttt{Folder} entities) in the Documents and Media
library, you must use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
interface's \texttt{addFolder} method. The steps here show you how to do
this. For more detailed information, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts}{Creating
Files, Folders, and Shortcuts}. For general information on using the
API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to create a folder with the \texttt{DLAppService}
method \texttt{addFolder}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{addFolder} method's
arguments. Since it's common to create a folder with data submitted by
the end user, you can extract the data from the request. This example
does so via \texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}:
\begin{verbatim}
long repositoryId = ParamUtil.getLong(actionRequest, "repositoryId");
long parentFolderId = ParamUtil.getLong(actionRequest, "parentFolderId");
String name = ParamUtil.getString(actionRequest, "name");
String description = ParamUtil.getString(actionRequest, "description");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFolder.class.getName(), actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{addFolder} method with the data
from the previous step:
\begin{verbatim}
Folder folder = _dlAppService.addFolder(
repositoryId, parentFolderId, name, description,
serviceContext);
\end{verbatim}
The method returns a \texttt{Folder} object, which this example sets
to a variable for later use. Note, however, that you don't have to do
this.
\end{enumerate}
You can find the full code for this example in the \texttt{updateFolder}
method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFolderMVCActionCommand.java}{\texttt{EditFolderMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{Folder} actions that the Documents and Media app
supports. Also note that this \texttt{updateFolder} method, as well as
the rest of \texttt{EditFolderMVCActionCommand}, contains additional
logic to suit the specific needs of the Documents and Media app.
\section{Related Topics}\label{related-topics-19}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-folders}{Updating
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders}{Deleting
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-folders}{Copying
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\chapter{Creating File Shortcuts}\label{creating-file-shortcuts}
To create file shortcuts (\texttt{FileShortcut} entities) in the
Documents and Media library, you must use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
interface's \texttt{addFileShortcut} method. The steps here show you how
to do this. For more detailed information, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts}{Creating
Files, Folders, and Shortcuts}. For general information on using the
API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to create a file shortcut with the
\texttt{DLAppService} method \texttt{addFileShortcut}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{addFileShortcut} method's
arguments. Since it's common to create a file shortcut with data
submitted by the end user, you can extract the data from the request.
This example does so via \texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
long repositoryId = ParamUtil.getLong(actionRequest, "repositoryId");
long folderId = ParamUtil.getLong(actionRequest, "folderId");
long toFileEntryId = ParamUtil.getLong(actionRequest, "toFileEntryId");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFileShortcutConstants.getClassName(), actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{addFileShortcut} method with the
data from the previous step:
\begin{verbatim}
FileShortcut fileShortcut = _dlAppService.addFileShortcut(
repositoryId, folderId, toFileEntryId,
serviceContext);
\end{verbatim}
The method returns a \texttt{FileShortcut} object, which this example
sets to a variable for later use. Note, however, that you don't have
to do this.
\end{enumerate}
You can find the full code for this example in the
\texttt{updateFileShortcut} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileShortcutMVCActionCommand.java}{\texttt{EditFileShortcutMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileShortcut} actions that the Documents and Media app
supports. Also note that this \texttt{updateFileShortcut} method, as
well as the rest of \texttt{EditFileShortcutMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-20}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts}{Deleting
File Shortcuts}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-file-shortcuts}{Updating
File Shortcuts}
\chapter{Deleting Entities}\label{deleting-entities}
You can delete entities with the Documents and Media API. Note that the
exact meaning of \emph{delete} depends on the portal configuration and
the delete operation you choose. This is because the
\href{/docs/7-2/user/-/knowledge_base/u/restoring-deleted-assets}{Recycle
Bin}, which is enabled by default, can be used to recover deleted items.
Deletions via
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}},
however, are permanent. To send items to the Recycle Bin, you must use
the Capabilities API.
Here, you'll learn about deleting these entities:
\begin{itemize}
\tightlist
\item
\hyperref[files]{Files}
\item
\hyperref[file-versions]{File Versions}
\item
\hyperref[file-shortcuts]{File Shortcuts}
\item
\hyperref[folders]{Folders}
\end{itemize}
You'll also learn about using the \hyperref[recycle-bin]{Recycle Bin}.
\chapter{Files}\label{files-1}
There are two \texttt{DLAppService} methods you can use to delete files:
\begin{itemize}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#deleteFileEntry-long-}{\texttt{deleteFileEntry(long\ fileEntryId)}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#deleteFileEntryByTitle-long-long-java.lang.String-}{\texttt{deleteFileEntryByTitle(long\ repositoryId,\ long\ folderId,\ String\ title)}}
\end{itemize}
These methods differ only in how they identify a file for deletion. The
combination of the \texttt{folderId} and \texttt{title} parameters in
\texttt{deleteFileEntryByTitle} uniquely identify a file because it's
impossible for two files in the same folder to share a name. For
step-by-step instructions on using these methods, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-files}{Deleting
Files}.
\chapter{File Versions}\label{file-versions}
When a file is modified, Documents and Media creates a new file version
and leaves the previous version intact. Over time, old file versions can
accumulate and consume storage space. Fortunately, you can use the
Documents and Media API to delete them. Note, however, that there's no
way to send file versions to the Recycle Bin---once you delete them,
they're gone forever.
You can delete file versions with the \texttt{DLAppService} method
\texttt{deleteFileVersion}:
\begin{verbatim}
deleteFileVersion(long fileEntryId, String version)
\end{verbatim}
See this method's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#deleteFileVersion-long-java.lang.String-}{Javadoc}
for a description of the parameters. For step-by-step instructions on
using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-versions}{Deleting
File Versions}.
\section{Identifying File Versions}\label{identifying-file-versions}
Since there may be many versions of a file, it's useful to
programmatically identify old versions for deletion. You can do this
with
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/util/comparator/FileVersionVersionComparator.html}{\texttt{FileVersionVersionComparator}}.
The following example creates such a comparator and uses its
\texttt{compare} method to identify old file versions. The code does so
by iterating through each
\href{/docs/7-2/user/-/knowledge_base/u/workflow}{approved} version of
the file (\texttt{fileVersion}). Each iteration uses the
\texttt{compare} method to test that file version
(\texttt{fileVersion.getVersion()}) against the same file's current
version (\texttt{fileEntry.getVersion()}). If this comparison is greater
than \texttt{0}, then the iteration's file version
(\texttt{fileVersion}) is old and is deleted by
\texttt{deleteFileVersion}:
\begin{verbatim}
FileVersionVersionComparator comparator = new FileVersionVersionComparator();
for (FileVersion fileVersion: fileEntry.getVersions(WorkflowConstants.STATUS_APPROVED)) {
if (comparator.compare(fileEntry.getVersion(), fileVersion.getVersion()) > 0) {
dlAppService.deleteFileVersion(fileVersion.getFileEntryId(), fileVersion.getVersion());
}
}
\end{verbatim}
\chapter{File Shortcuts}\label{file-shortcuts-1}
To delete file shortcuts, use the \texttt{DLAppService} method
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#deleteFileShortcut-long-}{\texttt{deleteFileShortcut}}
with the ID of the shortcut you want to delete:
\begin{verbatim}
deleteFileShortcut(long fileShortcutId)
\end{verbatim}
For step-by-step instructions on using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts}{Deleting
File Shortcuts}.
\chapter{Folders}\label{folders-1}
Deleting folders is similar to deleting files. There are two methods you
can use to delete a folder. Click each method to see its Javadoc:
\begin{itemize}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#deleteFolder-long-}{\texttt{deleteFolder(long\ folderId)}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#deleteFolder-long-long-java.lang.String-}{\texttt{deleteFolder(long\ repositoryId,\ long\ parentFolderId,\ String\ name)}}
\end{itemize}
Which method you use is up to you---they both delete a folder. For
step-by-step instructions on using these methods, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders}{Deleting
Folders}.
\chapter{Recycle Bin}\label{recycle-bin}
Instead of deleting entities, you can move them to the
\href{/docs/7-2/user/-/knowledge_base/u/restoring-deleted-assets}{Recycle
Bin}. Note that the Recycle Bin isn't part of the Documents and Media
API. Although you can use the Recycle Bin API directly, in the case of
Documents and Media it's better to use the Capabilities API. This is
because some third-party repositories (e.g., SharePoint) don't support
Recycle Bin functionality. The Capabilities API lets you verify that the
repository you're working in supports the Recycle Bin. It's therefore a
best practice to always use the Capabilities API when moving entities to
the Recycle Bin.
For step-by-step instructions on this, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin}{Moving
Entities to the Recycle Bin}.
\chapter{Deleting Files}\label{deleting-files}
To delete a file with the Documents and Media API, you must use one of
the two \texttt{deleteFileEntry*} methods discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities}{Deleting
Entities}. The steps here show you how. For general information on using
the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to delete a file:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the arguments of the
\texttt{deleteFileEntry*} method you wish to use. Since it's common to
delete a file specified by the end user, you can extract the data you
need from the request. This example does so via
\texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish. Also note that this example
gets only the file entry ID because it uses \texttt{deleteFileEntry}:
\begin{verbatim}
long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId");
\end{verbatim}
If you want to use \texttt{deleteFileEntryByTitle} instead, you can
also get the repository ID, folder ID, and title from the request.
\item
Call the service reference's \texttt{deleteFileEntry*} method you wish
to use with the data from the previous step. This example calls
\texttt{deleteFileEntry} with the file entry's ID:
\begin{verbatim}
_dlAppService.deleteFileEntry(fileEntryId);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{deleteFileEntry} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileEntryMVCActionCommand.java}{\texttt{EditFileEntryMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileEntry} actions that the Documents and Media app
supports. Also note that this \texttt{deleteFileEntry} method, as well
as the rest of \texttt{EditFileEntryMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-21}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin}{Moving
Entities to the Recycle Bin}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files}{Creating
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-files}{Updating
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\chapter{Deleting File Versions}\label{deleting-file-versions}
To delete a file version with the Documents and Media API, you must use
the \texttt{deleteFileVersion} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities}{Deleting
Entities}. The steps here show you how. For general information on using
the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to use \texttt{deleteFileVersion} to delete a file
version:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the file entry ID and version for the file you want to delete.
Since it's common to delete a file version specified by the end user,
you can extract these parameters from the request. This example does
so via \texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can do this any way you wish:
\begin{verbatim}
long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId");
String version = ParamUtil.getString(actionRequest, "version");
\end{verbatim}
\item
Use the service reference to call the \texttt{deleteFileVersion}
method with the file entry ID and version from the previous step:
\begin{verbatim}
_dlAppService.deleteFileVersion(fileEntryId, version);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{deleteFileEntry} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileEntryMVCActionCommand.java}{\texttt{EditFileEntryMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileEntry} actions that the Documents and Media app
supports. Also note that this \texttt{deleteFileEntry} method, as well
as the rest of \texttt{EditFileEntryMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-22}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-files}{Deleting
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts}{Deleting
File Shortcuts}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders}{Deleting
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin}{Moving
Entities to the Recycle Bin}
\chapter{Deleting File Shortcuts}\label{deleting-file-shortcuts}
To delete a file shortcut with the Documents and Media API, you must use
the \texttt{deleteFileShortcut} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities}{Deleting
Entities}. The steps here show you how. For general information on using
the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to delete a file shortcut:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the file shortcut's ID. Since it's common to delete a file
shortcut specified by the end user, you can extract its ID from the
request. This example does so via \texttt{javax.portlet.ActionRequest}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can do this any way you wish:
\begin{verbatim}
long fileShortcutId = ParamUtil.getLong(actionRequest, "fileShortcutId");
\end{verbatim}
\item
Use the service reference to call the \texttt{deleteFileShortcut}
method with the file shortcut ID from the previous step:
\begin{verbatim}
_dlAppService.deleteFileShortcut(fileShortcutId);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{deleteFileShortcut} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileShortcutMVCActionCommand.java}{\texttt{EditFileShortcutMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileShortcut} actions that the Documents and Media app
supports. Also note that this \texttt{deleteFileShortcut} method, as
well as the rest of \texttt{EditFileShortcutMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-23}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin}{Moving
Entities to the Recycle Bin}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts}{Creating
File Shortcuts}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-file-shortcuts}{Updating
File Shortcuts}
\chapter{Deleting Folders}\label{deleting-folders}
To delete a folder with the Documents and Media API, you must use one of
the two \texttt{deleteFolder} methods discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities}{Deleting
Entities}. The steps here show you how. For general information on using
the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to delete a folder:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the arguments of the
\texttt{deleteFolder} method you wish to use. Since it's common to
delete a folder specified by the end user, you can extract the data
you need from the request. This example does so via
\texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish. Also note that this example
gets only the folder ID because the next step deletes the folder with
\texttt{deleteFolder(folderId)}:
\begin{verbatim}
long folderId = ParamUtil.getLong(actionRequest, "folderId");
\end{verbatim}
If you want to use the other \texttt{deleteFolder} method, you can
also get the repository ID, parent folder ID, and folder name from the
request.
\item
Call the service reference's \texttt{deleteFolder} method you wish to
use with the data from the previous step. This example calls
\texttt{deleteFolder} with the folder's ID:
\begin{verbatim}
_dlAppService.deleteFolder(folderId);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{deleteFolders} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFolderMVCActionCommand.java}{\texttt{EditFolderMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{Folder} actions that the Documents and Media app
supports. Also note that this \texttt{deleteFolders} method, as well as
the rest of \texttt{EditFolderMVCActionCommand}, contains additional
logic to suit the specific needs of the Documents and Media app.
\section{Related Topics}\label{related-topics-24}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin}{Moving
Entities to the Recycle Bin}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-folders}{Creating
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-folders}{Updating
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-folders}{Copying
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-files}{Deleting
Files}
\chapter{Moving Entities to the Recycle
Bin}\label{moving-entities-to-the-recycle-bin}
Follow these steps to use the Capabilities API to move an entity to the
Recycle Bin. For an explanation of why you should use the Capabilities
API for this, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities}{Deleting
Entities}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Verify that the repository supports the Recycle Bin. Do this by
calling the
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api\#specifying-repositories}{repository
object's} \texttt{isCapabilityProvided} method with
\texttt{TrashCapability.class} as its argument. This example does so
in \texttt{if} statement's condition:
\begin{verbatim}
if (repository.isCapabilityProvided(TrashCapability.class)) {
// The code to move the entity to the Recycle Bin
// You'll write this in the next step
}
\end{verbatim}
\item
Move the entity to the Recycle Bin if the repository supports it. To
do this, first get a \texttt{TrashCapability} reference by calling the
repository object's \texttt{getCapability} method with
\texttt{TrashCapability.class} as its argument. Then call the
\texttt{TrashCapability} method that moves the entity to the Recycle
Bin. For example, this code calls \texttt{moveFileEntryToTrash} to
move a file to the Recycle Bin:
\begin{verbatim}
if (repository.isCapabilityProvided(TrashCapability.class)) {
TrashCapability trashCapability = repository.getCapability(TrashCapability.class);
trashCapability.moveFileEntryToTrash(user.getUserId(), fileEntry);
}
\end{verbatim}
See the \texttt{TrashCapability}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/capabilities/TrashCapability.html}{Javadoc}
for information on the methods you can use to move other types of
entities to the Recycle Bin.
\end{enumerate}
\section{Related Topics}\label{related-topics-25}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-files}{Deleting
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders}{Deleting
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts}{Deleting
File Shortcuts}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\chapter{Updating Entities}\label{updating-entities}
Like
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts}{creating}
and
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities}{deleting}
entities, updating entities is a key task when working with Documents
and Media. The methods in the Documents and Media API for creating and
updating entities are similar. There are, however, a few important
differences.
Here, you'll learn about updating these entities:
\begin{itemize}
\tightlist
\item
\hyperref[files]{Files}
\item
\hyperref[folders]{Folders}
\item
\hyperref[file-shortcuts]{File Shortcuts}
\end{itemize}
\chapter{Files}\label{files-2}
Updating a file is a bit more complicated than
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files}{creating
one}. This is due to the way the update operation handles a file's
metadata and content. To update only a file's content, you must also
supply the file's existing metadata. Otherwise, the update operation
could lose the metadata. The opposite, however, isn't true. You can
modify a file's metadata without re-supplying the content. In such an
update, the file's content is automatically copied to the new version of
the file. To make this easier to remember, follow these rules when
updating files:
\begin{itemize}
\tightlist
\item
Always provide all metadata.
\item
Only provide the file's content when you want to change it.
\end{itemize}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
has three \texttt{updateFileEntry} methods that you can use to update a
file. These methods differ only in the file content's type. Click each
method to see its Javadoc, which contains a full description of its
parameters:
\begin{itemize}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#updateFileEntry-long-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-boolean-byte:A-com.liferay.portal.kernel.service.ServiceContext-}{\texttt{updateFileEntry(...,\ byte{[}{]}\ bytes,\ ...)}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#updateFileEntry-long-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-boolean-java.io.File-com.liferay.portal.kernel.service.ServiceContext-}{\texttt{updateFileEntry(...,\ File\ file,\ ...)}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#updateFileEntry-long-java.lang.String-java.lang.String-java.lang.String-java.lang.String-java.lang.String-boolean-java.io.InputStream-long-com.liferay.portal.kernel.service.ServiceContext-}{\texttt{updateFileEntry(...,\ InputStream\ is,\ long\ size,\ ...)}}
\end{itemize}
Keep these things in mind when using these methods:
\begin{itemize}
\item
To retain the original file's title and description, you must provide
those parameters to \texttt{updateFileEntry}. Omitting them deletes
any existing title and description.
\item
If you supply \texttt{null} in place of the file's content (e.g.,
\texttt{bytes}, \texttt{file}, or \texttt{is}), the update
automatically uses the file's existing content. Do this only if you
want to update the file's metadata.
\item
If you use \texttt{false} for the \texttt{majorVersion} parameter, the
update increments the file version by \texttt{0.1} (e.g., from
\texttt{1.0} to \texttt{1.1}). If you use \texttt{true} for this
parameter, the update increments the file version to the next
\texttt{.0} value (e.g., from \texttt{1.0} to \texttt{2.0},
\texttt{1.1} to \texttt{2.0}, etc.).
\end{itemize}
For a step-by-step guide on using these \texttt{updateFileEntry}
methods, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-files}{Updating
Files}.
\chapter{Folders}\label{folders-2}
You can use the Documents and Media API to
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-and-moving-entities}{copy
or move} folders to a different location. Options for in-place folder
updates, however, are limited. You can only update a folder's name and
description. You can do this with the \texttt{DLAppService} method
\texttt{updateFolder}:
\begin{verbatim}
updateFolder(long folderId, String name, String description, ServiceContext serviceContext)
\end{verbatim}
All parameters except the description are mandatory. For a full
description of this method and its parameters, see its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#updateFolder-long-java.lang.String-java.lang.String-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}.
For step-by-step instructions on using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-folders}{Updating
Folders}.
\chapter{File Shortcuts}\label{file-shortcuts-2}
You can update a file shortcut (\texttt{FileShortcut} entities) to
change the file it points to or the folder it resides in. Do this via
the \texttt{DLAppService} method \texttt{updateFileShortcut}:
\begin{verbatim}
updateFileShortcut(long fileShortcutId, long folderId, long toFileEntryId, ServiceContext serviceContext)
\end{verbatim}
All of this method's parameters are mandatory. To retain any of the
shortcut's original values, you must provide them to this method. For a
full description of the parameters, see the method's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#updateFileShortcut-long-long-long-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}.
For step-by-step instructions on using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-file-shortcuts}{Updating
File Shortcuts}.
\chapter{Updating Files}\label{updating-files}
To update a file with the Documents and Media API, you must use one of
the three \texttt{updateFileEntry} methods discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-entities}{Updating
Entities}. The steps here show you how. For general information on using
the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Note that the example in these steps uses the \texttt{updateFileEntry}
method that contains \texttt{InputStream}, but you can adapt the example
to the other methods if you wish:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{updateFileEntry} method's
arguments. Since it's common to update a file with data submitted by
the end user, you can extract the data from the request. This example
does so via
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upload/UploadPortletRequest.html}{\texttt{UploadPortletRequest}}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
long repositoryId = ParamUtil.getLong(uploadPortletRequest, "repositoryId");
long folderId = ParamUtil.getLong(uploadPortletRequest, "folderId");
String sourceFileName = uploadPortletRequest.getFileName("file");
String title = ParamUtil.getString(uploadPortletRequest, "title");
String description = ParamUtil.getString(uploadPortletRequest, "description");
String changeLog = ParamUtil.getString(uploadPortletRequest, "changeLog");
boolean majorVersion = ParamUtil.getBoolean(uploadPortletRequest, "majorVersion");
try (InputStream inputStream = uploadPortletRequest.getFileAsStream("file")) {
String contentType = uploadPortletRequest.getContentType("file");
long size = uploadPortletRequest.getSize("file");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFileEntry.class.getName(), uploadPortletRequest);
}
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{updateFileEntry} method with the
data from the previous step. Note that this example does so inside the
previous step's try-with-resources statement:
\begin{verbatim}
try (InputStream inputStream = uploadPortletRequest.getFileAsStream("file")) {
...
FileEntry fileEntry = _dlAppService.updateFileEntry(
fileEntryId, sourceFileName, contentType, title,
description, changeLog, majorVersion, inputStream, size,
serviceContext);
}
\end{verbatim}
The method returns a \texttt{FileEntry} object, which this example
sets to a variable for later use. Note, however, that you don't have
to do this.
\end{enumerate}
You can find the full code for this example in the
\texttt{updateFileEntry} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileEntryMVCActionCommand.java}{\texttt{EditFileEntryMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileEntry} actions that the Documents and Media app
supports. Also note that this \texttt{updateFileEntry} method, as well
as the rest of \texttt{EditFileEntryMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-26}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-files}{Creating
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-files}{Deleting
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\chapter{Updating Folders}\label{updating-folders}
To update a folder with the Documents and Media API, you must use the
\texttt{updateFolder} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-entities}{Updating
Entities}. The steps here show you how. For general information on using
the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to update a folder:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{updateFolder} method's
arguments. Since it's common to update a folder with data submitted by
the end user, you can extract the data from the request. This example
does so via \texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
long folderId = ParamUtil.getLong(actionRequest, "folderId");
String name = ParamUtil.getString(actionRequest, "name");
String description = ParamUtil.getString(actionRequest, "description");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFolder.class.getName(), actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{updateFolder} method with the
data from the previous step:
\begin{verbatim}
_dlAppService.updateFolder(folderId, name, description, serviceContext);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the \texttt{updateFolder}
method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFolderMVCActionCommand.java}{\texttt{EditFolderMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{Folder} actions that the Documents and Media app
supports. Also note that this \texttt{updateFolder} method, as well as
the rest of \texttt{EditFolderMVCActionCommand}, contains additional
logic to suit the specific needs of the Documents and Media app.
\section{Related Topics}\label{related-topics-27}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-folders}{Creating
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders}{Deleting
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-folders}{Copying
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\chapter{Updating File Shortcuts}\label{updating-file-shortcuts}
To update a file shortcut with the Documents and Media API, you must use
the \texttt{updateFileShortcut} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-entities}{Updating
Entities}. The steps here show you how. For general information on using
the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to update a file shortcut:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{updateFileShortcut}
method's arguments. Since it's common to update a file shortcut with
data submitted by the end user, you can extract the data from the
request. This example does so via \texttt{javax.portlet.ActionRequest}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
long fileShortcutId = ParamUtil.getLong(actionRequest, "fileShortcutId");
long folderId = ParamUtil.getLong(actionRequest, "folderId");
long toFileEntryId = ParamUtil.getLong(actionRequest, "toFileEntryId");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFileShortcutConstants.getClassName(), actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{updateFileShortcut} method with
the data from the previous step:
\begin{verbatim}
_dlAppService.updateFileShortcut(
fileShortcutId, folderId, toFileEntryId, serviceContext);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{updateFileShortcut} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileShortcutMVCActionCommand.java}{\texttt{EditFileShortcutMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileShortcut} actions that the Documents and Media app
supports. Also note that this \texttt{updateFileShortcut} method, as
well as the rest of \texttt{EditFileShortcutMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-28}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts}{Creating
File Shortcuts}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts}{Deleting
File Shortcuts}
\chapter{File Checkout and Checkin}\label{file-checkout-and-checkin}
Users can
\href{/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-files}{check
out files} from the Document Library for editing. Only the user who
checked out the file can edit it. This prevents conflicting edits on the
same file from multiple users. The Documents and Media API allows these
checkin/checkout operations:
\begin{itemize}
\tightlist
\item
\href{file-checkout}{File Checkout}
\item
\href{file-checkin}{File Checkin}
\item
\href{canceling-a-checkout}{Canceling a Checkout}
\end{itemize}
\chapter{File Checkout}\label{file-checkout}
Here's what happens when you check out a file:
\begin{itemize}
\item
A private working copy of the file is created that only you and
administrators can access. Until you check the file back in or cancel
your changes, any edits you make are stored in the private working
copy.
\item
Other users can't change or edit any version of the file. This state
remains until you cancel or check in your changes.
\end{itemize}
The main \texttt{DLAppService} method for checking out a file is this
\texttt{checkOutFileEntry} method:
\begin{verbatim}
checkOutFileEntry(long fileEntryId, ServiceContext serviceContext)
\end{verbatim}
If this method throws an exception, then you should assume the checkout
failed and repeat the operation. For a full description of the method
and its parameters, see its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#checkOutFileEntry-long-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}.
For step-by-step instructions on using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/checking-out-files}{Checking
Out Files}.
\section{Fine-tuning Checkout}\label{fine-tuning-checkout}
You can control how the checkout is performed by setting the following
attributes in the \texttt{checkOutFileEntry} method's
\texttt{ServiceContext} parameter:
\begin{itemize}
\item
\texttt{manualCheckInRequired}: By default, the system automatically
checks out/in a file when a user edits it. Setting this attribute to
\texttt{true} prevents this, therefore requiring manual checkout and
checkin.
\item
\texttt{existingDLFileVersionId}: The system typically reuses the
private working copy across different checkout/checkin sequences.
There's little chance for conflicting edits because only one user at a
time can access the private working copy. To force the system to
create a new private working copy each time, omit this attribute or
set it to \texttt{0}.
\item
\texttt{fileVersionUuid}: This is used by
\href{/docs/7-2/user/-/knowledge_base/u/staging}{staging}, but can be
ignored for normal use. Setting this attribute causes the system to
create the new private working copy version with the given UUID.
\end{itemize}
To set these attributes, use the \texttt{ServiceContext} method
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/ServiceContext.html\#setAttribute-java.lang.String-java.io.Serializable-}{\texttt{setAttribute(String\ name,\ Serializable\ value)}}.
Here's an example of setting the \texttt{manualCheckInRequired}
attribute to \texttt{true}:
\begin{verbatim}
serviceContext.setAttribute("manualCheckInRequired", Boolean.TRUE)
\end{verbatim}
\chapter{File Checkin}\label{file-checkin}
After checking out and editing a file, you must check it back in for
other users to see the new version. Once you do so, you can't access the
private working copy. The next time the file is checked out, the private
working copy's contents are overwritten.
The \texttt{DLAppService} method for checking in a file is
\texttt{checkInFileEntry}:
\begin{verbatim}
checkInFileEntry(long fileEntryId, boolean majorVersion, String changeLog,
ServiceContext serviceContext)
\end{verbatim}
For a full description of the method and its parameters, see its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#checkInFileEntry-long-boolean-java.lang.String-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}.
This method uses the private working copy to create a new version of the
file. As
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-files}{Updating
Files} explains, the \texttt{majorVersion} parameter's setting
determines how the file's version number is incremented.
For step-by-step instructions on using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/checking-in-files}{Checking
In Files}.
\chapter{Canceling a Checkout}\label{canceling-a-checkout}
You can also cancel a checkout. Use caution with this operation---it
discards any edits made since checkout. If you're sure you want to
cancel a checkout, do so with the \texttt{DLAppService} method
\texttt{cancelCheckOut}:
\begin{verbatim}
cancelCheckOut(long fileEntryId)
\end{verbatim}
For a full description of this method and its parameter, see its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#cancelCheckOut-long-}{Javadoc}.
If you invoke this method without error, you can safely assume that it
discarded the private working copy and unlocked the file. Other users
should now be able to check out and edit the file.
For step-by-step instructions on using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/canceling-a-checkout}{Canceling
a Checkout}.
\chapter{Checking Out Files}\label{checking-out-files}
To check out a file with the Documents and Media API, use the
\texttt{checkOutFileEntry} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/file-checkout-and-checkin}{File
Checkout and Checkin}. The steps here show you how. For general
information on using the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to check out a file:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{checkOutFileEntry}
method's arguments. Since it's common to check out a file in response
to an action by the end user, you can extract the data from the
request. This example does so via \texttt{javax.portlet.ActionRequest}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId");
ServiceContext serviceContext = ServiceContextFactory.getInstance(actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{checkOutFileEntry} method with
the data from the previous step:
\begin{verbatim}
_dlAppService.checkOutFileEntry(fileEntryId, serviceContext);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{checkOutFileEntries} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileEntryMVCActionCommand.java}{\texttt{EditFileEntryMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileEntry} actions that the Documents and Media app
supports. Also note that this \texttt{checkOutFileEntries} method, as
well as the rest of \texttt{EditFileEntryMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-29}
\href{/docs/7-2/frameworks/-/knowledge_base/f/checking-in-files}{Checking
In Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/canceling-a-checkout}{Canceling
a Checkout}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-files}{Updating
Files}
\chapter{Checking In Files}\label{checking-in-files}
To check in a file with the Documents and Media API, use the
\texttt{checkInFileEntry} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/file-checkout-and-checkin}{File
Checkout and Checkin}. The steps here show you how. For general
information on using the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to use \texttt{checkInFileEntry} to check in a file:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{checkInFileEntry} method's
arguments. Since it's common to check in a file in response to an
action by the end user, you can extract the data from the request.
This example does so via \texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId");
boolean majorVersion = ParamUtil.getBoolean(actionRequest, "majorVersion");
String changeLog = ParamUtil.getString(actionRequest, "changeLog");
ServiceContext serviceContext = ServiceContextFactory.getInstance(actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{checkInFileEntry} method with the
data from the previous step:
\begin{verbatim}
_dlAppService.checkInFileEntry(
fileEntryId, majorVersion, changeLog, serviceContext);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{checkInFileEntries} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileEntryMVCActionCommand.java}{\texttt{EditFileEntryMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileEntry} actions that the Documents and Media app
supports. Also note that this \texttt{checkInFileEntries} method, as
well as the rest of \texttt{EditFileEntryMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-30}
\href{/docs/7-2/frameworks/-/knowledge_base/f/checking-out-files}{Checking
Out Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/canceling-a-checkout}{Canceling
a Checkout}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-files}{Updating
Files}
\chapter{Canceling a Checkout}\label{canceling-a-checkout-1}
To cancel a checkout with the Documents and Media API, use the
\texttt{cancelCheckOut} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/file-checkout-and-checkin}{File
Checkout and Checkin}. The steps here show you how. For general
information on using the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to cancel a checkout:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the ID of the file whose checkout you want to cancel. Since it's
common to cancel a checkout in response to a user action, you can
extract the file ID from the request. This example does so via
\texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get it any way you wish:
\begin{verbatim}
long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId");
\end{verbatim}
\item
Call the service reference's \texttt{cancelCheckOut} method with the
file's ID:
\begin{verbatim}
_dlAppService.cancelCheckOut(fileEntryId);
\end{verbatim}
\end{enumerate}
You can find the full code for this example in the
\texttt{cancelFileEntriesCheckOut} method of Liferay DXP's
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-web/src/main/java/com/liferay/document/library/web/internal/portlet/action/EditFileEntryMVCActionCommand.java}{\texttt{EditFileEntryMVCActionCommand}}
class. This class uses the Documents and Media API to implement almost
all the \texttt{FileEntry} actions that the Documents and Media app
supports. Also note that this \texttt{cancelFileEntriesCheckOut} method,
as well as the rest of \texttt{EditFileEntryMVCActionCommand}, contains
additional logic to suit the specific needs of the Documents and Media
app.
\section{Related Topics}\label{related-topics-31}
\href{/docs/7-2/frameworks/-/knowledge_base/f/checking-out-files}{Checking
Out Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/checking-in-files}{Checking
In Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-files}{Updating
Files}
\chapter{Copying and Moving Entities}\label{copying-and-moving-entities}
Although the Documents and Media API can copy and move entities, these
operations have some important caveats and limitations. Keep these
things in mind when copying entities:
\begin{itemize}
\tightlist
\item
There's no way to copy files---you can only copy folders. However,
copying a folder also copies its contents, which can include files.
\item
Folders can only be copied within their current repository.
\end{itemize}
The move operation doesn't have these restrictions. It's possible to
move files and folders between different repositories. In general,
however, the move operation is a bit more complicated than the copy
operation. For example, the API's behavior changes depending on whether
you move entities to a different repository or within the same one.
Here, you'll learn about the following:
\begin{itemize}
\tightlist
\item
\hyperref[copying-folders]{Copying Folders}
\item
\hyperref[moving-folders-and-files]{Moving Folders and Files}
\end{itemize}
\chapter{Copying Folders}\label{copying-folders}
The Documents and Media API can copy folders within a repository. You
can't, however, copy a folder between different repositories. Note that
copying a folder also copies its contents.
To copy a folder, use the \texttt{DLAppService} method
\texttt{copyFolder}:
\begin{verbatim}
copyFolder(long repositoryId, long sourceFolderId, long parentFolderId, String name,
String description, ServiceContext serviceContext)
\end{verbatim}
For a full description of the method and its parameters, see its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#copyFolder-long-long-long-java.lang.String-java.lang.String-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}.
For step-by-step instructions on using this method, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-folders}{Copying
Folders}.
\chapter{Moving Folders and Files}\label{moving-folders-and-files}
The move operation is more flexible than the copy operation. Copying
only works with folders, and you can't copy between repositories. The
move operation, however, works with files and folders within or between
repositories.
\noindent\hrulefill
\textbf{Note:} Depending on the repository implementation, you may get
unexpected behavior when moving folders between repositories. Moving a
folder also moves its contents via separate move operations for each
item in the folder. In some repository implementations, if any move
sub-operation fails, the parent move operation also fails. In other
repository implementations, the results of successful sub-operations
remain even if others fail, which leaves a partially complete move of
the whole folder.
\noindent\hrulefill
To move a folder, use the \texttt{DLAppService} method
\texttt{moveFolder}:
\begin{verbatim}
moveFolder(long folderId, long parentFolderId, ServiceContext serviceContext)
\end{verbatim}
For a full description of this method and its parameters, see its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#moveFolder-long-long-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}.
This method is similar to \texttt{copyFolder}, but it can't change the
folder's name or description, and it can move folders between
repositories. Folder contents are moved with the folder.
The operation for moving a file is almost identical to moving a folder.
To move a file, use the \texttt{DLAppService} method
\texttt{moveFileEntry}:
\begin{verbatim}
moveFileEntry(long fileEntryId, long newFolderId, ServiceContext serviceContext)
\end{verbatim}
For a full description of this method and its parameters, see its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#moveFileEntry-long-long-com.liferay.portal.kernel.service.ServiceContext-}{Javadoc}.
For step-by-step instructions on using \texttt{moveFolder} and
\texttt{moveFileEntry}, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}.
\chapter{Copying Folders}\label{copying-folders-1}
To copy a folder with the Documents and Media API, use the
\texttt{copyFolder} method discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-and-moving-entities}{Copying
and Moving Entities}. The steps here show you how. For general
information on using the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to use \texttt{copyFolder} to copy a folder:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the \texttt{copyFolder} method's
arguments. How you do this depends on your use case. The copy
operation in this example takes place in the default Site repository
and retains the folder's existing name and description. It therefore
needs the folder's group ID (to specify the default site repository),
name, and description. Also note that because the destination folder
in this example is the repository's root folder, the parent folder ID
isn't needed---Liferay DXP supplies a constant for specifying a
repository's root folder.
In the following code,
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}
gets the folder's ID from the request
(\texttt{javax.portlet.ActionRequest}), and the service reference's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#getFolder-long-}{\texttt{getFolder}}
method gets the corresponding folder object. The folder's
\texttt{getGroupId()}, \texttt{getName()}, and
\texttt{getDescription()} methods then get the folder's group ID,
name, and description, respectively:
\begin{verbatim}
long folderId = ParamUtil.getLong(actionRequest, "folderId");
Folder folder = _dlAppService.getFolder(folderId);
long groupId = folder.getGroupId();
String folderName = folder.getName();
String folderDescription = folder.getDescription();
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFolder.class.getName(), actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's \texttt{copyFolder} method with the data
from the previous step. Note that this example uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/model/DLFolderConstants.html}{\texttt{DLFolderConstants}}
constant \texttt{DEFAULT\_PARENT\_FOLDER\_ID} to specify the
repository's root folder as the destination folder:
\begin{verbatim}
_dlAppService.copyFolder(
groupId, folderId, DLFolderConstants.DEFAULT_PARENT_FOLDER_ID,
folderName, folderDescription, serviceContext);
\end{verbatim}
\end{enumerate}
Note that you can change any of these values to suit your copy
operation. For example, if your copy takes place in a repository other
than the default Site repository, you would specify that repository's ID
in place of the group ID. You could also specify a different destination
folder, and/or change the new folder's name and/or description.
\section{Related Topics}\label{related-topics-32}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-folders}{Creating
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-folders}{Updating
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders}{Deleting
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files}{Moving
Folders and Files}
\chapter{Moving Folders and Files}\label{moving-folders-and-files-1}
To move folders and files with the Documents and Media API, use the
\texttt{moveFolder} and \texttt{moveFileEntry} methods discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-and-moving-entities}{Copying
and Moving Entities}. The steps here show you how. For general
information on using the API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to use \texttt{moveFolder} and \texttt{moveFileEntry}
to move a folder and a file, respectively. This example does both to
demonstrate the procedures:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the method arguments. Since moving
folders and files is typically done in response to a user action, you
can get the data from the request. This example does so via
\texttt{javax.portlet.ActionRequest} and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}},
but you can get the data any way you wish:
\begin{verbatim}
// Get the folder IDs
long folderId = ParamUtil.getLong(actionRequest, "folderId");
long newFolderId = ParamUtil.getLong(actionRequest, "newFolderId");
// Get the file ID
long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
DLFileEntry.class.getName(), actionRequest);
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Call the service reference's method(s). This example calls
\texttt{moveFolder} to move a folder (\texttt{folderId}) to a
different folder (\texttt{newFolderId}). It then calls
\texttt{moveFileEntry} to move a file (\texttt{fileEntryId}) to the
same destination folder:
\begin{verbatim}
_dlAppService.moveFolder(folderId, newFolderId, serviceContext);
_dlAppService.moveFileEntry(fileEntryId, newFolderId, serviceContext);
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-33}
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-folders}{Copying
Folders}
\chapter{Getting Entities}\label{getting-entities}
The Documents and Media API contains many methods for getting entities
from a repository. Most methods in \texttt{DLAppService} get single
entities (e.g., a file or folder), a collection of entities that match
certain characteristics, or the number of such entities. Because there
are so many similar methods for getting entities, they aren't all
described here. You can find full descriptions for all
\texttt{DLAppService} methods in its
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{reference
documentation}.
Here, you'll learn about getting these entities:
\begin{itemize}
\tightlist
\item
\hyperref[files]{Files}
\item
\hyperref[folders]{Folders}
\item
\hyperref[multiple-entity-types]{Multiple Entity Types}
\end{itemize}
\chapter{Files}\label{files-3}
Getting files is one of the most common tasks you'll perform with the
Documents and Media API. There are two main method families for getting
files:
\texttt{getFileEntries}: Gets files from a specific repository.
\texttt{getGroupFileEntries}: Gets files from a Site (group), regardless
of repository.
Since these method families are common, their methods share many
parameters:
\texttt{repositoryId}: The ID of the repository to get files from. To
specify the default Site repository, use the \texttt{groupId} (Site ID).
\texttt{folderId}: The ID of the folder to get files from. Note that
these methods don't traverse the folder structure---they only get files
directly from the specified folder. To specify the repository's root
folder, use the constant
\texttt{DLFolderConstants.DEFAULT\_PARENT\_FOLDER\_ID}.
\texttt{start} and \texttt{end}: Integers that specify the lower and
upper bounds, respectively, of collection items to include in a page of
results. If you don't want to use pagination, use
\texttt{QueryUtil.ALL\_POS} for these parameters.
\texttt{obc}: The comparator to use to order collection items.
Comparators are
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/OrderByComparator.html}{\texttt{OrderByComparator}}
implementations that sort collection items.
\texttt{fileEntryTypeId}: The ID of the file type to retrieve. Use this
to retrieve files of a specific type.
\texttt{mimeTypes}: The MIME types of the files to retrieve. Use this to
retrieve files of the specified MIME types. You can specify MIME types
via the constants in
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ContentTypes.html}{\texttt{ContentTypes}}.
Note that the \texttt{obc} parameter must be an implementation of
\texttt{OrderByComparator}. Although you can implement your own
comparators, Liferay DXP already contains a few useful implementations
in the package
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/util/comparator/package-summary.html}{\texttt{com.liferay.document.library.kernel.util.comparator}}:
\texttt{RepositoryModelCreateDateComparator}: Sorts by creation date.
\texttt{RepositoryModelModifiedDateComparator}: Sorts by modification
date.
\texttt{RepositoryModelReadCountComparator}: Sorts by number of views.
\texttt{RepositoryModelSizeComparator}: Sorts by file size.
\texttt{RepositoryModelTitleComparator}: Sorts by title.
See \href{/docs/7-2/frameworks/-/knowledge_base/f/getting-files}{Getting
Files} for step-by-step instructions on using the above method families.
\chapter{Folders}\label{folders-3}
The Documents and Media API can get folders in a similar way to getting
files. The main difference is that folder retrieval methods may have an
additional argument to tell the system whether to include \emph{mount
folders}. Mount folders are mount points for external repositories
(e.g.~Alfresco or SharePoint) that appear as regular folders in a Site's
default repository. They let users navigate seamlessly between
repositories. To account for this, some folder retrieval methods include
the boolean parameter \texttt{includeMountFolders}. Setting this
parameter to \texttt{true} includes mount folders in the results, while
omitting it or setting it to \texttt{false} excludes them.
For example, to get a list of a parent folder's subfolders from a
repository, including any mount folders, use this
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#getFolders-long-long-boolean-}{\texttt{getFolders}}
method:
\begin{verbatim}
getFolders(long repositoryId, long parentFolderId, boolean includeMountFolders)
\end{verbatim}
Note that there are several other \texttt{getFolders} methods in
\texttt{DLAppService}. Use the one that best matches your use case. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-folders}{Getting
Folders} for step-by-step instructions on using these
\texttt{getFolders} methods.
\chapter{Multiple Entity Types}\label{multiple-entity-types}
There are also methods in the Documents and Media API that retrieve
lists containing several entity types. These methods use many of the
same parameters as those already described for retrieving files and
folders. For example, the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#getFileEntriesAndFileShortcuts-long-long-int-int-int-}{\texttt{getFileEntriesAndFileShortcuts}}
method gets files and shortcuts from a given repository and folder. Its
\texttt{status} parameter specifies a
\href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow} status. As
before, the \texttt{start} and \texttt{end} parameters control
pagination of the entities:
\begin{verbatim}
getFileEntriesAndFileShortcuts(long repositoryId, long folderId, int status, int start, int end)
\end{verbatim}
For step-by-step instructions on calling this method and others like it,
see
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-multiple-entity-types}{Getting
Multiple Entity Types}. To see all such methods, see the
\texttt{DLAppService}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{Javadoc}.
\chapter{Getting Files}\label{getting-files}
To get files with the Documents and Media API, use a method from the
\texttt{getFileEntries} or \texttt{getGroupFileEntries} method families
discussed in
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-entities}{Getting
Entities}. The steps here show you how, using this
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#getFileEntries-long-long-java.lang.String:A-int-int-com.liferay.portal.kernel.util.OrderByComparator-}{\texttt{getFileEntries}}
method as an example:
\begin{verbatim}
List getFileEntries(
long repositoryId,
long folderId,
String[] mimeTypes,
int start,
int end,
OrderByComparator obc
)
\end{verbatim}
For general information on using the Documents and Media API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to get a list of files. This example uses the above
\texttt{getFileEntries} method to get all the PNG images from the root
folder of a Site's default repository, sorted by title:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the method's arguments. You can do
this any way you wish. As the next step describes, Liferay DXP
provides constants and a comparator for all the arguments this example
needs besides the group ID. This example gets the group ID by using
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}
with the request (\texttt{javax.portlet.ActionRequest}):
\begin{verbatim}
long groupId = ParamUtil.getLong(actionRequest, "groupId");
\end{verbatim}
It's also possible to get the group ID via the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html}{\texttt{ThemeDisplay}}.
Calling the \texttt{ThemeDisplay} method \texttt{getScopeGroupId()}
gets the ID of your app's current site (group):
\begin{verbatim}
ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY);
long groupId = themeDisplay.getScopeGroupId();
\end{verbatim}
\end{enumerate}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Use the data from the previous step to call the service reference
method you want to use to get the files. This example calls the above
\texttt{getFileEntries} method with the group ID from the previous
step, and constants and a comparator for the remaining arguments:
\begin{verbatim}
List fileEntries =
_dlAppService.getFileEntries(
groupId,
DLFolderConstants.DEFAULT_PARENT_FOLDER_ID,
new String[] {ContentTypes.IMAGE_PNG},
QueryUtil.ALL_POS,
QueryUtil.ALL_POS,
new RepositoryModelTitleComparator<>()
);
\end{verbatim}
Here's a description of the arguments used in this example:
\texttt{groupId}: Using the group ID as the repository ID specifies
that the operation takes place in the default site repository.
\texttt{DLFolderConstants.DEFAULT\_PARENT\_FOLDER\_ID}: Uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/model/DLFolderConstants.html}{\texttt{DLFolderConstants}}
constant \texttt{DEFAULT\_PARENT\_FOLDER\_ID} to specify the
repository's root folder.
\texttt{new\ String{[}{]}\ \{ContentTypes.IMAGE\_PNG\}}: Uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ContentTypes.html}{\texttt{ContentTypes}}
constant \texttt{IMAGE\_PNG} to specify PNG images.
\texttt{QueryUtil.ALL\_POS}: Uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/QueryUtil.html}{\texttt{QueryUtil}}
constant \texttt{ALL\_POS} for the start and end positions in the
results. This specifies all results, bypassing pagination.
\texttt{new\ RepositoryModelTitleComparator\textless{}\textgreater{}()}:
Creates a new
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/util/comparator/RepositoryModelTitleComparator.html}{\texttt{RepositoryModelTitleComparator}},
which sorts the results by title.
\end{enumerate}
Remember, this is just one of many \texttt{getFileEntries} and
\texttt{getGroupFileEntries} methods. To see all such methods, see the
\texttt{DLAppService}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{Javadoc}.
\section{Related Topics}\label{related-topics-34}
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-folders}{Getting
Folders}
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-multiple-entity-types}{Getting
Multiple Entity Types}
\chapter{Getting Folders}\label{getting-folders}
To get folders with the Documents and Media API, use one of the
\texttt{getFolders} methods in \texttt{DLAppService}. This is discussed
in more detail in
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-entities}{Getting
Entities}. The steps here show you how to call these \texttt{getFolders}
methods. As an example, this method is used to get a parent folder's
subfolders:
\begin{verbatim}
getFolders(long repositoryId, long parentFolderId, boolean includeMountFolders)
\end{verbatim}
For general information on using the Documents and Media API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Follow these steps to call a \texttt{getFolders} method:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the method's arguments any way you
wish. This \texttt{getFolders} method needs a repository ID, a parent
folder ID, and a boolean value that indicates whether to include mount
folders in the results. To specify the default site repository, you
can use the group ID as the repository ID. This example gets the group
ID from the request (\texttt{javax.portlet.ActionRequest}) via
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}:
\begin{verbatim}
long groupId = ParamUtil.getLong(actionRequest, "groupId");
\end{verbatim}
It's also possible to get the group ID via the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html}{\texttt{ThemeDisplay}}.
Calling the \texttt{ThemeDisplay} method \texttt{getScopeGroupId()}
gets the ID of your app's current Site (group).
\begin{verbatim}
ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY);
long groupId = themeDisplay.getScopeGroupId();
\end{verbatim}
Note that getting the parent folder ID isn't necessary because this
example uses the root folder, for which Liferay DXP provides a
constant. Also, the boolean value can be provided directly---it
doesn't need to be retrieved from somewhere.
\item
Call the service reference's \texttt{getFolders} method with the data
from the previous step and any other values you want to provide. Note
that this example uses
\texttt{DLFolderConstants.DEFAULT\_PARENT\_FOLDER\_ID} to specify the
repository's root folder as the parent folder. It also uses
\texttt{true} to include any mount folders in the results:
\begin{verbatim}
_dlAppService.getFolders(groupId, DLFolderConstants.DEFAULT_PARENT_FOLDER_ID, true)
\end{verbatim}
\end{enumerate}
This is one of many methods you can use to get folders. The rest are
listed in the \texttt{DLAppService}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{Javadoc}.
\section{Related Topics}\label{related-topics-35}
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-files}{Getting
Files}
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-multiple-entity-types}{Getting
Multiple Entity Types}
\chapter{Getting Multiple Entity
Types}\label{getting-multiple-entity-types}
There are several methods in
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html}{\texttt{DLAppService}}
that get lists containing multiple entity types. This is discussed in
more detail in
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-entities}{Getting
Entities}. The steps here show you how to use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html\#getFileEntriesAndFileShortcuts-long-long-int-int-int-}{\texttt{getFileEntriesAndFileShortcuts}}
method, but you can apply them to other such methods as well. For
general information on using the Documents and Media API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents
and Media API}.
Note that the example in these steps gets all the files and shortcuts in
the default Site repository's root folder:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to \texttt{DLAppService}:
\begin{verbatim}
@Reference
private DLAppService _dlAppService;
\end{verbatim}
\item
Get the data needed to populate the method's arguments any way you
wish. To specify the default Site repository, you can use the group ID
as the repository ID. This example gets the group ID from the request
(\texttt{javax.portlet.ActionRequest}) via
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}:
\begin{verbatim}
long groupId = ParamUtil.getLong(actionRequest, "groupId");
\end{verbatim}
Getting the parent folder ID, workflow status, and start and end
parameters isn't necessary because Liferay DXP provides constants for
them. The next step shows this in detail.
\item
Call the service reference method with the data from the previous step
and any other values you want to provide. This example calls
\texttt{getFileEntriesAndFileShortcuts} with the group ID from the
previous step and constants for the remaining arguments:
\begin{verbatim}
_dlAppService.getFileEntriesAndFileShortcuts(
groupId,
DLFolderConstants.DEFAULT_PARENT_FOLDER_ID,
WorkflowConstants.STATUS_APPROVED,
QueryUtil.ALL_POS,
QueryUtil.ALL_POS
)
\end{verbatim}
Here's a description of the arguments used in this example:
\begin{itemize}
\tightlist
\item
\texttt{groupId}: Using the group ID as the repository ID specifies
that the operation takes place in the default site repository.
\item
\texttt{DLFolderConstants.DEFAULT\_PARENT\_FOLDER\_ID}: Uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/model/DLFolderConstants.html}{\texttt{DLFolderConstants}}
constant \texttt{DEFAULT\_PARENT\_FOLDER\_ID} to specify the
repository's root folder.
\item
\texttt{WorkflowConstants.STATUS\_APPROVED}: Uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/workflow/WorkflowConstants.html}{\texttt{WorkflowConstants}}
constant \texttt{STATUS\_APPROVED} to specify only files/folders
that have been approved via workflow.
\item
\texttt{QueryUtil.ALL\_POS}: Uses the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/QueryUtil.html}{\texttt{QueryUtil}}
constant \texttt{ALL\_POS} for the start and end positions in the
results. This specifies all results, bypassing pagination.
\end{itemize}
\end{enumerate}
\section{Related Topics}\label{related-topics-36}
\href{/docs/7-1/frameworks/-/knowledge_base/frameworks/getting-files}{Getting
Files}
\href{/docs/7-1/frameworks/-/knowledge_base/frameworks/getting-folders}{Getting
Folders}
\chapter{Adaptive Media}\label{adaptive-media-1}
The
\href{/docs/7-2/user/-/knowledge_base/u/adapting-your-media-across-multiple-devices}{Adaptive
Media} app tailors the size and quality of images to the device
displaying them. Here, you'll learn about these things:
\begin{itemize}
\tightlist
\item
\hyperref[the-adaptive-media-taglib]{The Adaptive Media Taglib}
\item
\hyperref[adaptive-medias-finder-api]{Adaptive Media's Finder API}
\item
\hyperref[image-scaling-in-adaptive-media]{Image Scaling in Adaptive
Media}
\end{itemize}
\chapter{The Adaptive Media Taglib}\label{the-adaptive-media-taglib}
To display adapted images in your apps, Adaptive Media offers a
convenient tag library in the module
\href{https://github.com/liferay/com-liferay-adaptive-media/tree/master/adaptive-media-image-taglib}{\texttt{com.liferay.adaptive.media.image.taglib}}.
The only mandatory attribute for the taglib is \texttt{fileVersion}. It
indicates the file version of the adapted image to display. The taglib
uses this file version to query Adaptive Media's finder API and display
the adapted image appropriate for the device making the request. You can
also add as many attributes as needed, such as \texttt{class},
\texttt{style}, \texttt{data-sample}, and so on. Any attributes you add
are then added to the adapted images in the markup the taglib renders.
For step-by-step instructions on using this taglib, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app}{Displaying
Adapted Images in Your App}.
\chapter{Adaptive Media's Finder API}\label{adaptive-medias-finder-api}
If you need more control than the taglib offers for finding adapted
images, you can query Adaptive Media's finder API directly. For example,
if you have an app that needs a specific image in a specific dimension,
it's best to query Adaptive Media's finder API directly. You can then
display the image however you like (e.g., with an HTML
\texttt{\textless{}img\textgreater{}} tag).
Adaptive Media's finder API lets you write queries that get adapted
images based on certain search criteria and filters. For example, you
can get adapted images that match a file version or resolution, or are
ordered by an attribute like image width. You can even get adapted
images that match approximate attribute values.
\section{Calling the API}\label{calling-the-api}
The entry point to Adaptive Media's API is
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageFinder.html}{\texttt{AMImageFinder}}.
To use it, you must first inject the OSGi component in your class (which
must also be an OSGi component) as follows:
\begin{verbatim}
@Reference
private AMImageFinder _amImageFinder;
\end{verbatim}
This makes an \texttt{AMImageFinder} instance available. It has one
method, \texttt{getAdaptiveMediaStream}, that returns a stream of
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/AdaptiveMedia.html}{\texttt{AdaptiveMedia}}
objects. This method takes a \texttt{Function} that creates an
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/finder/AMQuery.html}{\texttt{AMQuery}}
(the query for adapted images) via
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.html}{\texttt{AMImageQueryBuilder}},
which can search adapted images based on different attributes (e.g.,
width, height, order, etc.). The \texttt{AMImageQueryBuilder} methods
you call depend on the exact query you want to construct.
For example, here's a general \texttt{getAdaptiveMediaStream} call:
\begin{verbatim}
Stream> adaptiveMediaStream =
_amImageFinder.getAdaptiveMediaStream(
amImageQueryBuilder -> amImageQueryBuilder.methodToCall(arg).done());
\end{verbatim}
The argument to \texttt{getAdaptiveMediaStream} is a lambda expression
that returns an \texttt{AMQuery} constructed via
\texttt{AMImageQueryBuilder}. Note that \texttt{methodToCall(arg)} is a
placeholder for the \texttt{AMImageQueryBuilder} method you want to call
and its argument. The exact call depends on the criteria you want to use
to select adapted images. The \texttt{done()} call that follows this,
however, isn't a placeholder--it creates and returns the
\texttt{AMQuery} regardless of which \texttt{AMImageQueryBuilder}
methods you call.
For more information on creating \texttt{AMQuery} instances, see the
\texttt{AMImageQueryBuilder}
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.html}{Javadoc}.
For step-by-step instructions on calling Adaptive Media's API, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/finding-adapted-images}{Finding
Adapted Images}.
\section{Adaptive Media API
Constants}\label{adaptive-media-api-constants}
When calling the Adaptive Media API, there are some constants you can
use for specifying common attributes:
\begin{itemize}
\tightlist
\item
\texttt{AMImageAttribute.AM\_IMAGE\_ATTRIBUTE\_WIDTH}: image width
\item
\texttt{AMImageAttribute.AM\_IMAGE\_ATTRIBUTE\_HEIGHT}: image height
\item
\texttt{AMImageQueryBuilder.SortOrder.ASC}: ascending sort
\item
\texttt{AMImageQueryBuilder.SortOrder.DESC}: descending sort
\end{itemize}
\section{Approximate Attributes}\label{approximate-attributes}
Adaptive Media also lets you get adapted images that match approximate
attribute values. For example, you can ask for adapted images whose
height is around 200px, or whose size is around 100kb. The API returns a
stream with elements ordered by how close they are to the specified
attribute. For example, imagine that there are four image resolutions
that have adapted images with the heights 150px, 350px, 600px, and
900px. Searching for adapted images whose height is approximately 400px
returns this order in the stream: 350px, 600px, 150px, 900px.
So how close, exactly, is \emph{close}? It depends on the attribute. In
the case of width, height, and length, a numeric comparison orders the
images. In the case of content type, file name, or UUID, the comparison
is more tricky because these attributes are strings and thus delegated
to Java's
\href{https://docs.oracle.com/javase/8/docs/api/java/lang/String.html\#compareTo-java.lang.String-}{\texttt{String.compareTo}}
method.
\chapter{Image Scaling in Adaptive
Media}\label{image-scaling-in-adaptive-media}
As described in
\href{/docs/7-2/user/-/knowledge_base/u/adapting-your-media-across-multiple-devices}{Adaptive
Media's user guide}, Adaptive Media scales images to match the image
resolutions defined by the Liferay DXP administrator. The default
scaling is usually suitable, but Adaptive Media contains an extension
point that lets you replace the way it scales images. The
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/scaler/AMImageScaler.html}{\texttt{AMImageScaler}}
interface defines Adaptive Media's image scaling logic. Out of the box,
Adaptive Media provides two implementations of this interface:
\href{https://github.com/liferay/com-liferay-adaptive-media/blob/master/adaptive-media-image-impl/src/main/java/com/liferay/adaptive/media/image/internal/scaler/AMDefaultImageScaler.java}{\texttt{AMDefaultImageScaler}}:
The default image scaler. It's always enabled and uses \texttt{java.awt}
for its image processing and scaling.
\href{https://github.com/liferay/com-liferay-adaptive-media/blob/master/adaptive-media-image-impl/src/main/java/com/liferay/adaptive/media/image/internal/scaler/AMGIFImageScaler.java}{\texttt{AMGIFImageScaler}}:
A scaler that works only with GIF images. It depends on the installation
of the external tool \href{https://www.lcdf.org/gifsicle/}{gifsicle} in
the Liferay DXP instance. This scaler is disabled by default.
Administrators can enable it in \emph{Control Panel} → \emph{System
Settings}.
You must register image scalers in Liferay DXP's OSGi container using
the \texttt{AMImageScaler} interface. Each scaler must also set the
\texttt{mime.type} property to the MIME type it handles. For example, if
you set a scaler's MIME type to \texttt{image/jpeg}, then that scaler
can only handle \texttt{image/jpeg} images. If you specify the special
MIME type \texttt{*}, the scaler can process any image. Note that
\texttt{AMDefaultImageScaler} is registered using \texttt{mime.type=*},
while \texttt{AMGIFImageScaler} is registered using
\texttt{mime.type=image/gif}. Both scalers, like all scalers, implement
\texttt{AMImageScaler}.
You can add as many image scalers as you need, even for the same MIME
type. However, Adaptive Media uses only one scaler per image, using this
process to determine the best one:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select only the image scalers registered with the same MIME type as
the image.
\item
Select the enabled scalers from those selected in the first step (the
\texttt{AMImageScaler} method \texttt{isEnabled()} returns
\texttt{true} for enabled scalers).
\item
Of the scalers selected in the second step, select the one with the
highest \texttt{service.ranking}.
\end{enumerate}
If these steps return no results, they're repeated with the special MIME
type \texttt{*}. Also note that if an image scaler is registered for
specific MIME types and has a higher \texttt{service.ranking}, it's more
likely to be chosen than if it's registered for the special MIME type
\texttt{*} or has a lower \texttt{service.ranking}.
For step-by-step instructions on creating your own image scaler, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-image-scaler}{Creating
an Image Scaler}.
\chapter{Displaying Adapted Images in Your
App}\label{displaying-adapted-images-in-your-app}
Follow these steps to display adapted images in your app with the
Adaptive Media
\href{https://github.com/liferay/com-liferay-adaptive-media/tree/master/adaptive-media-image-taglib}{taglib}.
For more information, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media\#the-adaptive-media-taglib}{The
Adaptive Media Taglib}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Include the taglib dependency in your project. For example, if you're
using Gradle you must add the following line in your project's
\texttt{build.gradle} file:
\begin{verbatim}
provided group: "com.liferay", name: "com.liferay.adaptive.media.image.taglib", version: "1.0.0"
\end{verbatim}
\item
Declare the taglib in your JSP:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/adaptive-media-image" prefix="liferay-adaptive-media" %>
\end{verbatim}
\item
Use the taglib wherever you want the adapted image to appear in your
app's JSP files:
\begin{verbatim}
\end{verbatim}
For example, this \texttt{view.jsp} uses the taglib to display the
adapted images in a grid with the \texttt{col-md-6}
\href{/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro}{column
container class}:
\begin{verbatim}
<%@ include file="/init.jsp" %>
<%
String[] mimeTypes = {"image/bmp", "image/gif", "image/jpeg", "image/pjpeg", "image/png", "image/tiff", "image/x-citrix-jpeg", "image/x-citrix-png", "image/x-ms-bmp", "image/x-png", "image/x-tiff"};
List fileEntries = DLAppServiceUtil.getFileEntries(scopeGroupId, DLFolderConstants.DEFAULT_PARENT_FOLDER_ID, mimeTypes);
int columns = 0;
for (FileEntry fileEntry : fileEntries) {
boolean row = ((columns % 2) == 0);
%>
\end{verbatim}
\end{enumerate}
Looking at the generated markup, you can see that it uses the
\texttt{\textless{}picture\textgreater{}} tag as described in
\href{/docs/7-2/user/-/knowledge_base/u/creating-content-with-adapted-images}{Creating
Content with Adapted Images}.
\begin{figure}
\centering
\includegraphics{./images/adaptive-media-sample.png}
\caption{The Adaptive Media Samples app shows all the site's adapted
images.}
\end{figure}
\section{Related Topics}\label{related-topics-37}
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media}{Adaptive
Media}
\href{/docs/7-2/frameworks/-/knowledge_base/f/finding-adapted-images}{Finding
Adapted Images}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-image-scaler}{Creating
an Image Scaler}
\chapter{Finding Adapted Images}\label{finding-adapted-images}
If you need more control than the
\href{/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app}{Adaptive
Media taglib} offers for finding adapted images to display in your app,
you can query Adaptive Media's finder API directly. The steps here show
you how for these scenarios:
\begin{itemize}
\tightlist
\item
\hyperref[getting-adapted-images-for-file-versions]{Getting Adapted
Images for File Versions}
\item
\hyperref[getting-the-adapted-images-for-a-specific-image-resolution]{Getting
the Adapted Images for a Specific Image Resolution}
\item
\hyperref[getting-adapted-images-in-a-specific-order]{Getting Adapted
Images in a Specific Order}
\item
\hyperref[using-approximate-attributes]{Using Approximate Attributes}
\item
\hyperref[using-the-adaptive-media-stream]{Using the Adaptive Media
Stream}
\end{itemize}
For background information on these topics, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media\#adaptive-medias-finder-api}{Adaptive
Media's Finder API}.
\section{Getting Adapted Images for File
Versions}\label{getting-adapted-images-for-file-versions}
Follow these steps to get adapted images for file versions. Note that
the method calls here only return adapted images for
\href{/docs/7-2/user/-/knowledge_base/u/managing-image-resolutions}{enabled
image resolutions}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get an \texttt{AMImageFinder} reference:
\begin{verbatim}
@Reference
private AMImageFinder _amImageFinder;
\end{verbatim}
\item
To get adapted images for a specific file version, call the
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.html}{\texttt{AMImageQueryBuilder}}
method \texttt{forFileVersion} with a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/model/FileVersion.html}{\texttt{FileVersion}}
object as an argument:
\begin{verbatim}
Stream> adaptiveMediaStream =
_amImageFinder.getAdaptiveMediaStream(
amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(fileVersion).done());
\end{verbatim}
\item
To get the adapted images for the latest approved file version, use
the \texttt{forFileEntry} method with a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/model/FileEntry.html}{\texttt{FileEntry}}
object:
\begin{verbatim}
Stream> adaptiveMediaStream =
_amImageFinder.getAdaptiveMediaStream(
amImageQueryBuilder -> amImageQueryBuilder.forFileEntry(fileEntry).done());
\end{verbatim}
\end{enumerate}
To get adapted images regardless of status (enabled/disabled image
resolutions), invoke the \texttt{withConfigurationStatus} method with
the constant \texttt{AMImageQueryBuilder.ConfigurationStatus.ANY}:
\begin{verbatim}
Stream> adaptiveMediaStream =
_amImageFinder.getAdaptiveMediaStream(
amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(fileVersion)
.withConfigurationStatus(AMImageQueryBuilder.ConfigurationStatus.ANY).done());
\end{verbatim}
Use the constant
\texttt{AMImageQueryBuilder.ConfigurationStatus.DISABLED} to get adapted
images for only disabled image resolutions.
\section{Getting the Adapted Images for a Specific Image
Resolution}\label{getting-the-adapted-images-for-a-specific-image-resolution}
By providing an image resolution's UUID to \texttt{AMImageFinder}, you
can get that resolution's adapted images. This UUID is defined when
\href{/docs/7-2/user/-/knowledge_base/u/adding-image-resolutions}{adding
the resolution} in the Adaptive Media app. To get a resolution's adapted
images, you must pass that resolution's UUID to the
\texttt{forConfiguration} method.
Follow these steps to get adapted images for an image resolution:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get an \texttt{AMImageFinder} reference:
\begin{verbatim}
@Reference
private AMImageFinder _amImageFinder;
\end{verbatim}
\item
Call the
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.ConfigurationStep.html}{\texttt{AMImageQueryBuilder.ConfigurationStep}}
method \texttt{forConfiguration} with the image resolution's UUID. For
example, this code gets the adapted images that match a file version,
and belong to an image resolution with the UUID
\texttt{hd-resolution}. It returns the adapted images regardless of
whether the resolution is enabled or disabled:
\begin{verbatim}
Stream> adaptiveMediaStream =
_amImageFinder.getAdaptiveMediaStream(
amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(fileVersion)
.forConfiguration("hd-resolution").done());
\end{verbatim}
\end{enumerate}
\section{Getting Adapted Images in a Specific
Order}\label{getting-adapted-images-in-a-specific-order}
It's also possible to define the order in which
\texttt{getAdaptiveMediaStream} returns adapted images. Follow these
steps to do so:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get an \texttt{AMImageFinder} reference:
\begin{verbatim}
@Reference
private AMImageFinder _amImageFinder;
\end{verbatim}
\item
Call the \texttt{orderBy} method with your sort criteria just before
calling the \texttt{done()} method. The \texttt{orderBy} method takes
two arguments: the first specifies the image attribute to sort by
(e.g., width/height), while the second specifies the sort order (e.g.,
ascending/descending). The Adaptive Media API provides
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media\#adaptive-media-api-constants}{constants}
that you can use for these arguments.
For example, this code gets all the adapted images regardless of
whether the image resolution is enabled, and puts them in ascending
order by image width:
\begin{verbatim}
Stream> adaptiveMediaStream =
_amImageFinder.getAdaptiveMediaStream(
amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(_fileVersion)
.withConfigurationStatus(AMImageQueryBuilder.ConfigurationStatus.ANY)
.orderBy(AMImageAttribute.AM_IMAGE_ATTRIBUTE_WIDTH, AMImageQueryBuilder.SortOrder.ASC)
.done());
\end{verbatim}
\end{enumerate}
\section{Using Approximate
Attributes}\label{using-approximate-attributes}
You can use the API to get adapted images that match approximate
attribute values. Follow these steps to do so:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get an \texttt{AMImageFinder} reference:
\begin{verbatim}
@Reference
private AMImageFinder _amImageFinder;
\end{verbatim}
\item
Call the \texttt{with} method with your search criteria just before
calling the \texttt{done()} method. The \texttt{with} method takes two
arguments: the image attribute and that attribute's approximate value.
For example, this code gets adapted images whose height is
approximately 400px:
\begin{verbatim}
Stream> adaptiveMediaStream =
_amImageFinder.getAdaptiveMediaStream(
amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(_fileVersion)
.with(AMImageAttribute.AM_IMAGE_ATTRIBUTE_HEIGHT, 400).done());
\end{verbatim}
\end{enumerate}
\section{Using the Adaptive Media
Stream}\label{using-the-adaptive-media-stream}
The Adaptive Media stream flows like a babbling brook through the sands
of time. Just kidding; it's not like that at all. Once you have the
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/AdaptiveMedia.html}{\texttt{AdaptiveMedia}}
stream, you can get the information you need from it. For example, this
code prints the URI for each adapted image:
\begin{verbatim}
adaptiveMediaStream.forEach(
adaptiveMedia -> {
System.out.println(adaptiveMedia.getURI());
}
);
\end{verbatim}
You can also get other values and attributes from the
\texttt{AdaptiveMedia} stream. Here are a few examples:
\begin{verbatim}
// Get the InputStream
adaptiveMedia.getInputStream()
// Get the content length
adaptiveMedia.getValueOptional(AMAttribute.getContentLengthAMAttribute())
// Get the image height
adaptiveMedia.getValueOptional(AMImageAttribute.AM_IMAGE_ATTRIBUTE_HEIGHT)
\end{verbatim}
\section{Related Topics}\label{related-topics-38}
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media}{Adaptive
Media}
\href{/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app}{Displaying
Adapted Images in Your App}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-image-scaler}{Creating
an Image Scaler}
\chapter{Creating an Image Scaler}\label{creating-an-image-scaler}
Adaptive Media scales images to match the image resolutions defined by
the Liferay DXP administrator. The default scaling is usually suitable,
but you can customize it by creating an image scaler. The steps here
show you how. For detailed information on these steps, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media\#image-scaling-in-adaptive-media}{Image
Scaling in Adaptive Media}.
Follow these steps to create a custom image scaler. The example scaler
in these steps customizes the scaling of PNG images:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Create your scaler class to implement \texttt{AMImageScaler}. You must
also annotate your scaler class with \texttt{@Component}, setting
\texttt{mime.type} properties for each of the scaler's MIME types, and
registering an \texttt{AMImageScaler} service. If there's more than
one scaler for the same MIME type, you must also set the
\texttt{@Component} annotation's \texttt{service.ranking} property.
For your scaler to take precedence over other scalers of the same MIME
type, its service ranking property must be higher than that of the
other scalers. If \texttt{service.ranking} isn't set, it defaults to
\texttt{0}.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** The `service.ranking` property isn't set for the image scalers
included with Adaptive Media (`AMDefaultImageScaler` and
`AMGIFImageScaler`). Their service ranking therefore defaults to `0`. To
replace either scaler, you must set your scaler to the same MIME type and
give it a service ranking higher than `0`.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
This example image scaler scales PNG and x-PNG images and has a service
ranking of `100`:
```java
@Component(
immediate = true,
property = {"mime.type=image/png", "mime.type=image/x-png", "service.ranking:Integer=100"},
service = {AMImageScaler.class}
)
public class SampleAMPNGImageScaler implements AMImageScaler {...
```
This requires these imports:
```java
import com.liferay.adaptive.media.image.scaler.AMImageScaler;
import org.osgi.service.component.annotations.Component;
```
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Implement the \texttt{isEnabled()} method to return \texttt{true} when
you want to enable the scaler. In many cases, you always want the
scaler enabled, so you can simply return \texttt{true} in this method.
This is the case with the image scaler in this example:
\begin{verbatim}
@Override
public boolean isEnabled() {
return true;
}
\end{verbatim}
This method gets more interesting when the scaler depends on other
tools or features. For example, the \texttt{isEnabled()} method in
\texttt{AMGIFImageScaler} determines whether gifsicle is enabled. This
scaler must only be enabled when the tool it depends on, gifsicle, is
also enabled:
\begin{verbatim}
@Override
public boolean isEnabled() {
return _amImageConfiguration.gifsicleEnabled();
}
\end{verbatim}
\item
Implement the \texttt{scaleImage} method. This method contains the
scaler's business logic and must return an \texttt{AMImageScaledImage}
instance. For example, the \texttt{scaleImage} implementation in this
example uses \texttt{AMImageConfigurationEntry} to get the maximum
height and width values for the scaled image, and \texttt{FileVersion}
to get the image to scale. The scaling is done via a private inner
class, assuming that the methods \texttt{\_scalePNG},
\texttt{\_getScalePNGHeight}, \texttt{\_getScalePNGWidth}, and
\texttt{\_getScalePNGSize} implement the actual scaling:
\begin{verbatim}
@Override
public AMImageScaledImage scaleImage(FileVersion fileVersion,
AMImageConfigurationEntry amImageConfigurationEntry) {
Map properties = amImageConfigurationEntry.getProperties();
int maxHeight = GetterUtil.getInteger(properties.get("max-height"));
int maxWidth = GetterUtil.getInteger(properties.get("max-width"));
try {
InputStream inputStream =
_scalePNG(fileVersion.getContentStream(false), maxHeight, maxWidth);
int height = _getScalePNGHeight();
int width = _getScalePNGWidth();
long size = _getScalePNGSize();
return new AMImageScaledImageImpl(inputStream, height, width, size);
}
catch (PortalException pe) {
throw new AMRuntimeException.IOException(pe);
}
}
private class AMImageScaledImageImpl implements AMImageScaledImage {
@Override
public int getHeight() {
return _height;
}
@Override
public InputStream getInputStream() {
return _inputStream;
}
@Override
public long getSize() {
return _size;
}
@Override
public int getWidth() {
return _width;
}
private AMImageScaledImageImpl(InputStream inputStream, int height,
int width, long size) {
_inputStream = inputStream;
_height = height;
_width = width;
_size = size;
}
private final int _height;
private final InputStream _inputStream;
private final long _size;
private final int _width;
}
\end{verbatim}
This requires these imports:
\begin{verbatim}
import com.liferay.adaptive.media.exception.AMRuntimeException;
import com.liferay.adaptive.media.image.configuration.AMImageConfigurationEntry;
import com.liferay.adaptive.media.image.scaler.AMImageScaledImage;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.repository.model.FileVersion;
import com.liferay.portal.kernel.util.GetterUtil;
import java.io.InputStream;
import java.util.Map;
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-39}
\href{/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media}{Adaptive
Media}
\href{/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app}{Displaying
Adapted Images in Your App}
\href{/docs/7-2/frameworks/-/knowledge_base/f/finding-adapted-images}{Finding
Adapted Images}
\chapter{Social API}\label{social-api-1}
You can use the social API to integrate Liferay DXP's social features
with your apps. Here, you'll learn about the following topics:
\begin{itemize}
\tightlist
\item
\hyperref[social-bookmarks]{Social Bookmarks}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-comments-to-your-app}{Adding
Comments to Your App}
\item
\hyperref[ratings]{Ratings}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/flagging-inappropriate-asset-content}{Flagging
Inappropriate Asset Content}
\end{itemize}
\chapter{Social Bookmarks}\label{social-bookmarks}
To apply social bookmarks to your app's content, you must use the
\texttt{liferay-social-bookmarks} taglib. This taglib contains the
\texttt{liferay-social-bookmarks:bookmarks} tag, which adds the social
bookmarks component. This tag contains these attributes:
\texttt{className}: The entity's class name.
\texttt{classPK}: The entity's primary key.
\texttt{displayStyle}: The social bookmarks' display style. Possible
values are \texttt{inline}, which displays them in a row, and
\texttt{menu}, which hides them in a menu.
\texttt{title}: A title for the content being shared. This attribute is
often populated by calling the entity's \texttt{getTitle()} method (or
other method that retrieves the title).
\texttt{types}: A comma-delimited list of the social media services to
use (e.g., \texttt{facebook,twitter}). To use every social media service
available in the portal, omit this attribute or use
\texttt{\textless{}\%=\ null\ \%\textgreater{}} for its value.
\texttt{url}: A URL to the portal content being shared. The
\texttt{PortalUtil} method \texttt{getCanonicalURL} is often called to
populate this attribute. This method constructs an SEO-friendly URL from
the page's full URL. For more information, see the method's
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/PortalUtil.html\#getCanonicalURL-java.lang.String-com.liferay.portal.kernel.theme.ThemeDisplay-com.liferay.portal.kernel.model.Layout-}{Javadoc}.
For instructions on using this tag, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/applying-social-bookmarks}{Applying
Social Bookmarks}. For instructions on creating your own social
bookmarks, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-social-bookmarks}{Creating
Social Bookmarks}.
\begin{figure}
\centering
\includegraphics{./images/social-bookmarks-inline.png}
\caption{With \texttt{displayStyle} set to \texttt{inline}, the first
three social bookmarks appear in a row and the rest appear in a menu.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/social-bookmarks-menu.png}
\caption{With \texttt{displayStyle} set to \texttt{menu}, all social
bookmarks appear in the \emph{Share} menu.}
\end{figure}
\chapter{Ratings}\label{ratings}
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{The asset
framework} supports a content rating system. This feature appears in
many of Liferay DXP's built-in apps. For example, users can rate
articles published in the Blogs app. There are three different rating
types:
\begin{itemize}
\tightlist
\item
Likes
\item
Stars (five, by default)
\item
Thumbs (up/down)
\end{itemize}
To enable ratings in your app, you must use the
\texttt{liferay-ui:ratings} tag and set its \texttt{type} attribute to
the rating type (\texttt{like}, \texttt{stars}, or \texttt{thumbs}). For
instructions on this, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/rating-assets}{Rating
Assets}.
\section{Rating Type Selection}\label{rating-type-selection}
Admins can select the rating type for an app's entities via the Control
Panel and Site Administration. Portal admins can set the default rating
type for the portal, while Site admins can override the default rating
type for their Site.
A ratings-enabled app must define its rating type in an OSGi component
that implements the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/definition/PortletRatingsDefinition.html}{\texttt{PortletRatingsDefinition}}
interface. This class declares the usage of ratings (specifying the
portlet and the entity) and the default rating type (that can be
overridden by portal and site admins). This interface has two methods
that you must implement:
\texttt{getDefaultRatingsType}: Returns the entity's default rating
type, which portal and site admins can override. You can do this via the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/RatingsType.html}{\texttt{RatingsType}}
enum, which contains \texttt{LIKE}, \texttt{STARS}, or \texttt{THUMBS}.
\texttt{getPortletId}: Returns the portlet ID of the main portlet that
uses the entity. You can do this via the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/PortletKeys.html}{\texttt{PortletKeys}}
enum, which defines many constants that correspond to the portlet IDs of
the built-in portlets.
To add support for rating type selection in your app, follow the
instructions in
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-rating-type-selection}{Implementing
Rating Type Selection}. Once you've done so, you can configure the
default rating type via the Control Panel at \emph{Configuration} →
\emph{Instance Settings} → \emph{Social}. To override the default values
for a site, go to Site Administration (your Site's menu) →
\emph{Configuration} → \emph{Site Settings} → \emph{Social}.
\section{Rating Value Transformation}\label{rating-value-transformation}
The database stores normalized rating values. This permits switching
between rating types without modifying the underlying data. When
administrators change an entity's rating type, its best match is
computed. Here's a list of the default transformations between rating
types:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
When changing from stars to:
\textbf{Like:} A value of 3, 4, or 5 stars is considered a like; a
value of 1 or 2 stars is omitted.
\textbf{Thumbs up/down:} A value of 3, 4, or 5 stars is considered a
thumbs up; a value of 1 or 2 stars is considered a thumbs down.
\item
When changing from thumbs up/down to:
\textbf{Like:} A like is considered a thumbs up.
\textbf{Stars:} A thumbs down is considered 1 star; a thumbs up is
considered 5 stars.
\item
When changing from like to:
\textbf{Stars:} A like is considered 5 stars.
\textbf{Thumbs up/down:} A like is considered a thumbs up.
\end{enumerate}
There may be some cases, however, where you want to apply different
criteria to determine the new rating values. A mechanism exists that
permits this, but it modifies the stored rating values. To define such
transformations, create an OSGi component that implements
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/transformer/RatingsDataTransformer.html}{\texttt{RatingsDataTransformer}}.
\noindent\hrulefill
\textbf{Note:} The portal doesn't provide a default
\texttt{RatingsDataTransformer} implementation. Unless you provide such
an implementation, the stored rating values always remain the same while
the portal interprets existing values for the selected rating type.
\noindent\hrulefill
When implementing \texttt{RatingsDataTransformer}, implement the
\texttt{transformRatingsData} method to transform the data. This
method's arguments include the \texttt{RatingsType} variables
\texttt{fromRatingsType} and \texttt{toRatingsType}, which contain the
rating type to transform from and to, respectively. These values let you
write your custom transformation's logic. You can write this logic by
implementing the interface
\texttt{ActionableDynamicQuery.PerformActionMethod} as an anonymous
inner class in the \texttt{transformRatingsData} method, implementing
the \texttt{performAction} method with your transformation's logic.
For instructions on implementing \texttt{RatingsDataTransformer}, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-rating-value-transformation}{Customizing
Rating Value Transformation}.
\chapter{Applying Social Bookmarks}\label{applying-social-bookmarks}
When you enable social bookmarks, icons for sharing on Twitter,
Facebook, and LinkedIn appear below your app's content. Taglibs provide
the markup you need to add this feature to your app.
\begin{figure}
\centering
\includegraphics{./images/social-bookmarks-inline.png}
\caption{These social bookmarks are in the inline display style.}
\end{figure}
Follow these steps to add social bookmarks to your app:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make sure your entity is
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{asset
enabled}.
\item
In your project's \texttt{build.gradle} file, add a dependency to the
module
\href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.social.bookmarks.taglib/}{\texttt{com.liferay.social.bookmarks.taglib}}:
\begin{verbatim}
compileOnly group: "com.liferay", name: "com.liferay.social.bookmarks.taglib", version: "1.0.0"
\end{verbatim}
\item
Choose a view in which to show the social bookmarks. For example, you
can display them in one of your app's views. However, note that you
don't need to implement social bookmarks in your app's
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer}{asset
renderers}. The Asset Publisher displays social bookmarks in asset
renderers by default.
\item
In your view's JSP, include the \texttt{liferay-social-bookmarks}
taglib declaration:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/social-bookmarks" prefix="liferay-social-bookmarks" %>
\end{verbatim}
\item
Get an instance of your entity. You can do this however you wish. This
example uses
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}
to get the entity's ID from the render request, then uses the entity's
\texttt{-LocalServiceUtil} class to create an entity object:
\begin{verbatim}
<%
long entryId = ParamUtil.getLong(renderRequest, "entryId");
entry = EntryLocalServiceUtil.getEntry(entryId);
%>
\end{verbatim}
\item
Use the \texttt{liferay-social-bookmarks:bookmarks} tag to add the
social bookmarks component. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#social-bookmarks}{Social
Bookmarks} for information on this tag's attributes. Here's an example
of using this tag to add social bookmarks for a blog entry in the
Blogs app:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-40}
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#social-bookmarks}{Social
Bookmarks}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-social-bookmarks}{Creating
Social Bookmarks}
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset
Framework}
\chapter{Creating Social Bookmarks}\label{creating-social-bookmarks}
By default, Liferay DXP contains social bookmarks for Twitter, Facebook,
and LinkedIn. You can also create your own social bookmark by
registering a component that implements the
\href{https://docs.liferay.com/dxp/apps/social/latest/javadocs/com/liferay/social/bookmarks/SocialBookmark.html}{\texttt{SocialBookmark}}
interface from the module \texttt{com.liferay.social.bookmarks.api}. The
steps here show you how to do this.
\section{Implementing the SocialBookmark
Interface}\label{implementing-the-socialbookmark-interface}
Follow these steps to implement the \texttt{SocialBookmark} interface:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create your \texttt{*SocialBookmark} class and register a component
that defines the \texttt{social.bookmarks.type} property. This
property's value is what you enter for the
\texttt{liferay-social-bookmarks:bookmarks} tag's \texttt{type}
attribute when you use your social bookmark.
For example, here's the definition for a Twitter social bookmark
class:
\begin{verbatim}
@Component(immediate = true, property = "social.bookmarks.type=twitter")
public class TwitterSocialBookmark implements SocialBookmark {...
\end{verbatim}
\item
Create a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ResourceBundleLoader.html}{\texttt{ResourceBundleLoader}}
reference to help localize the social bookmark's name.
\begin{verbatim}
@Reference(
target = "(bundle.symbolic.name=com.liferay.social.bookmark.twitter)"
)
private ResourceBundleLoader _resourceBundleLoader;
\end{verbatim}
\item
Implement the \texttt{getName} method to return the social bookmark's
name as a string. This method takes a
\href{https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html}{\texttt{Locale}}
object that you can use for localization via
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/language/LanguageUtil.html}{\texttt{LanguageUtil}}
and
\href{https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html}{\texttt{ResourceBundle}}:
\begin{verbatim}
@Override
public String getName(Locale locale) {
ResourceBundle resourceBundle = _resourceBundleLoader.loadResourceBundle(locale);
return LanguageUtil.get(resourceBundle, "twitter");
}
\end{verbatim}
\item
Implement the \texttt{getPostURL} method to return the share URL. This
method constructs the share URL from a title and URL, and uses
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/URLCodec.html}{\texttt{URLCodec}}
to encode the title in the URL:
\begin{verbatim}
@Override
public String getPostURL(String title, String url) {
return String.format(
"https://twitter.com/intent/tweet?text=%s&tw_p=tweetbutton&url=%s",
URLCodec.encodeURL(title), url);
}
\end{verbatim}
\item
Create a \texttt{ServletContext} reference:
\begin{verbatim}
@Reference(
target = "(osgi.web.symbolicname=com.liferay.social.bookmark.twitter)"
)
private ServletContext _servletContext;
\end{verbatim}
\item
Implement the \texttt{render} method, which is called when the inline
display style is selected. Typically, this method renders a link to
the share URL (e.g., a share button), but you can use it for whatever
you need. To keep a consistent look and feel with the default social
bookmarks, you can use a
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay icon}.
This example gets a \texttt{RequestDispatcher} for the JSP that
contains a Clay icon (\texttt{page.jsp}), and then includes that JSP
in the response:
\begin{verbatim}
@Override
public void render(
String target, String title, String url, HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
RequestDispatcher requestDispatcher =
_servletContext.getRequestDispatcher("/page.jsp");
requestDispatcher.include(request, response);
}
\end{verbatim}
\end{enumerate}
\section{Creating Your JSP}\label{creating-your-jsp}
The \texttt{page.jsp} file referenced in the above
\texttt{SocialBookmark} implementation uses
\href{/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links}{a
Clay link} (\texttt{clay:link}) to specify and style the Twitter icon
included with Clay. Follow these steps to create a JSP for your own
social bookmark:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{clay} and \texttt{liferay-theme} taglib declarations:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/clay" prefix="clay" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
\end{verbatim}
\item
Import
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/GetterUtil.html}{\texttt{GetterUtil}}
and \texttt{SocialBookmark}:
\begin{verbatim}
<%@ page import="com.liferay.portal.kernel.util.GetterUtil" %>
<%@ page import="com.liferay.social.bookmarks.SocialBookmark" %>
\end{verbatim}
\item
From the request, get a \texttt{SocialBookmark} instance and the
social bookmark's title and URL:
\begin{verbatim}
<%
SocialBookmark socialBookmark = (SocialBookmark)request.getAttribute("liferay-social-bookmarks:bookmark:socialBookmark");
String title = GetterUtil.getString((String)request.getAttribute("liferay-social-bookmarks:bookmark:title"));
String url = GetterUtil.getString((String)request.getAttribute("liferay-social-bookmarks:bookmark:url"));
%>
\end{verbatim}
The title and URL are set via the \texttt{liferay-social-bookmarks}
taglib when
\href{/docs/7-2/frameworks/-/knowledge_base/f/applying-social-bookmarks}{applying
the social bookmark}.
\item
Add the Clay link. See the \texttt{clay:link}
\href{https://clayui.com/docs/components/link.html}{documentation} for
a full description of its attributes.
\begin{verbatim}
\end{verbatim}
This example sets the following \texttt{clay:link} attributes:
\texttt{buttonStyle}: This example renders the
\href{/docs/7-2/reference/-/knowledge_base/r/clay-buttons}{button's
type} as a secondary button.
\texttt{elementClasses}: The custom CSS to use for styling the button
(optional).
\texttt{href}: The button's URL. You should specify this by calling
your \texttt{SocialBookmark} instance's \texttt{getPostURL} method.
\texttt{icon}: The button's icon. This example specifies the Twitter
icon included in Clay (\texttt{twitter}).
\texttt{title}: The button's title. This example uses the
\texttt{SocialBookmark} instance's \texttt{getName} method.
\end{enumerate}
To see a complete, real-world example of a social bookmark
implementation, see
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/social/social-bookmark-twitter}{Liferay's
Twitter social bookmark code}.
\section{Related Topics}\label{related-topics-41}
\href{/docs/7-2/frameworks/-/knowledge_base/f/applying-social-bookmarks}{Applying
Social Bookmarks}
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-clay-taglib-in-your-portlets}{Using
the Clay Taglib in Your Portlets}
\chapter{Adding Comments to Your App}\label{adding-comments-to-your-app}
Liferay provides taglibs that enable comments on your app's content.
Here, you'll learn how to use these taglibs, using a sample Guestbook
app as an example.
Follow these steps to enable commenting on your app's content:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make sure your entity is
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{asset
enabled}.
\item
Choose a read-only view of the entity you want to enable comments on.
You can display the comments component in your app's view, or if
you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer}{implemented
asset rendering} you can display it in the full content view in the
Asset Publisher app.
\item
Include the \texttt{liferay-ui}, \texttt{liferay-comment}, and
\texttt{portlet} taglib declarations in your JSP:
\begin{verbatim}
<%@ taglib prefix="liferay-ui" uri="http://liferay.com/tld/ui" %>
<%@ taglib prefix="liferay-comment" uri="http://liferay.com/tld/comment" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0" %>
\end{verbatim}
\item
Use
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}
to get the entity's ID from the render request. Then create an entity
object using the \texttt{-LocalServiceUtil} class. Here's an example
that does this for a guestbook entry in the example Guestbook app:
\begin{verbatim}
<%
long entryId = ParamUtil.getLong(renderRequest, "entryId");
entry = EntryLocalServiceUtil.getEntry(entryId);
%>
\end{verbatim}
\item
Create a collapsible panel for the comments using the
\texttt{liferay-ui:panel-container} and \texttt{liferay-ui:panel}
tags. This lets users hide the discussion area:
\begin{verbatim}
\end{verbatim}
\item
Create a URL for the discussion using the \texttt{portlet:actionURL}
tag:
\begin{verbatim}
\end{verbatim}
\item
Use the \texttt{liferay-comment:discussion} tag to add the discussion.
To let the user return to the JSP after making a comment, set the
tag's \texttt{redirect} attribute to the current URL. You can use
\texttt{PortalUtil.getCurrentURL((renderRequest))} to get the current
URL from the \texttt{request} object. In this example, the current URL
was earlier set to the \texttt{currentURL} variable:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
If you haven't already connected your portlet's view to the JSP for your
entity, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-jsp-templates-for-an-asset-renderer}{Configuring
JSP Templates for an Asset Renderer}.
\section{Related Topics}\label{related-topics-42}
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset
Framework}
\href{/docs/7-2/frameworks/-/knowledge_base/f/rating-assets}{Rating
Assets}
\chapter{Rating Assets}\label{rating-assets}
In only a few lines of code, you can use a taglib to enable ratings for
your app's content. The steps here show you how. For more information on
this taglib and ratings in general, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#ratings}{Ratings}.
\begin{figure}
\centering
\includegraphics{./images/social-ratings-thumbs.png}
\caption{Users can rate content to let others know how they really feel
about it.}
\end{figure}
Follow these steps to enable ratings in your app. Note that these steps
use a sample Guestbook app as an example. This app lets users leave
simple messages in a guestbook.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make sure your entity is
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{asset
enabled}.
\item
Choose a read-only view of the entity for which you want to enable
ratings. You can display ratings in one of your portlet's views, or if
you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer}{implemented
asset rendering} you can display them in the full content view in the
Asset Publisher app.
\item
In the JSP, include the \texttt{liferay-ui} taglib declaration:
\begin{verbatim}
<%@ taglib prefix="liferay-ui" uri="http://liferay.com/tld/ui" %>
\end{verbatim}
\item
Use
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}
to get the entity's ID from the render request. Then create an entity
object using the \texttt{-LocalServiceUtil} class. Here's an example
that does this for a guestbook entry in the example Guestbook app:
\begin{verbatim}
<%
long entryId = ParamUtil.getLong(renderRequest, "entryId");
entry = EntryLocalServiceUtil.getEntry(entryId);
%>
\end{verbatim}
\item
Use the \texttt{liferay-ui:ratings} tag to add the ratings component
for the entity. This example uses the stars rating type:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-43}
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#ratings}{Ratings}
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-rating-type-selection}{Implementing
Rating Type Selection}
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-rating-value-transformation}{Customizing
Rating Value Transformation}
\chapter{Implementing Rating Type
Selection}\label{implementing-rating-type-selection}
For administrators to change your app's rating type (e.g.~likes, stars,
thumbs), you must implement rating type selection. The steps here show
you how. For a detailed explanation of these steps and rating type
selection, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#rating-type-selection}{Rating
Type Selection}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/definition/PortletRatingsDefinition.html}{\texttt{PortletRatingsDefinition}}
interface, registering the class as an OSGi component. In the
\texttt{@Component} annotation, set the \texttt{model.class.name}
property to the fully qualified name of the class that will use this
rating definition. This example rating definition is for a blog entry,
so the \texttt{model.class.name} property is set to
\texttt{com.liferay.portlet.blogs.model.BlogsEntry}:
\begin{verbatim}
@Component(
property = {
"model.class.name=com.liferay.portlet.blogs.model.BlogsEntry"
}
)
public class BlogsPortletRatingsDefinition implements PortletRatingsDefinition {...
\end{verbatim}
\item
Implement the \texttt{PortletRatingsDefinition} methods
\texttt{getDefaultRatingsType} and \texttt{getPortletId} to return the
entity's default rating type and the portlet ID of the main portlet
that uses the entity, respectively. In this example, the rating type
is thumbs and the portlet ID is for the Blogs portlet:
\begin{verbatim}
@Override
public RatingsType getDefaultRatingsType() {
return RatingsType.THUMBS;
}
@Override
public String getPortletId() {
return PortletKeys.BLOGS;
}
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-44}
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#rating-type-selection}{Rating
Type Selection}
\href{/docs/7-2/frameworks/-/knowledge_base/f/rating-assets}{Rating
Assets}
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-rating-value-transformation}{Customizing
Rating Value Transformation}
\chapter{Customizing Rating Value
Transformation}\label{customizing-rating-value-transformation}
To customize rating value transformation, you must create an OSGi
component that implements
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/transformer/RatingsDataTransformer.html}{\texttt{RatingsDataTransformer}}.
The steps here show you how. For a detailed explanation of these steps
and rating value transformation, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#rating-value-transformation}{Rating
Value Transformation}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an OSGi component class that implements
\texttt{RatingsDataTransformer}:
\begin{verbatim}
@Component
public class DummyRatingsDataTransformer implements RatingsDataTransformer {...
\end{verbatim}
\item
In this class, implement the \texttt{transformRatingsData} method.
Note that it contains the \texttt{RatingsType} variables
\texttt{fromRatingsType} and \texttt{toRatingsType}:
\begin{verbatim}
@Override
public ActionableDynamicQuery.PerformActionMethod transformRatingsData(
final RatingsType fromRatingsType, final RatingsType toRatingsType)
throws PortalException {
}
\end{verbatim}
\item
In the \texttt{transformRatingsData} method, implement the interface
\texttt{ActionableDynamicQuery.PerformActionMethod} as an anonymous
inner class:
\begin{verbatim}
return new ActionableDynamicQuery.PerformActionMethod() {
};
\end{verbatim}
\item
In the anonymous \texttt{ActionableDynamicQuery.PerformActionMethod}
implementation, implement the \texttt{performAction} method to perform
your transformation:
\begin{verbatim}
@Override
public void performAction(Object object)
throws PortalException {
if (fromRatingsType.getValue().equals(RatingsType.LIKE) &&
toRatingsType.getValue().equals(RatingsType.STARS)) {
RatingsEntry ratingsEntry = (RatingsEntry) object;
ratingsEntry.setScore(0);
RatingsEntryLocalServiceUtil.updateRatingsEntry(
ratingsEntry);
}
}
\end{verbatim}
This example irreversibly transforms the rating type from likes to
stars, resetting the value to \texttt{0}. The \texttt{if} statement
uses the \texttt{fromRatingsType} and \texttt{toRatingsType} values to
specify that the transformation only occurs when going from likes to
stars. The transformation is performed via \texttt{RatingsEntry} and
its \texttt{-LocalServiceUtil}. After getting a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/model/RatingsEntry.html}{\texttt{RatingsEntry}}
object, its \texttt{setScore} method sets the rating score to
\texttt{0}. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/service/RatingsEntryLocalServiceUtil.html}{\texttt{RatingsEntryLocalServiceUtil}}
method \texttt{updateRatingsEntry} then updates the
\texttt{RatingsEntry} in the database.
\end{enumerate}
Here's the complete class for this example:
\begin{verbatim}
@Component
public class DummyRatingsDataTransformer implements RatingsDataTransformer {
@Override
public ActionableDynamicQuery.PerformActionMethod transformRatingsData(
final RatingsType fromRatingsType, final RatingsType toRatingsType)
throws PortalException {
return new ActionableDynamicQuery.PerformActionMethod() {
@Override
public void performAction(Object object)
throws PortalException {
if (fromRatingsType.getValue().equals(RatingsType.LIKE) &&
toRatingsType.getValue().equals(RatingsType.STARS)) {
RatingsEntry ratingsEntry = (RatingsEntry) object;
ratingsEntry.setScore(0);
RatingsEntryLocalServiceUtil.updateRatingsEntry(
ratingsEntry);
}
}
};
}
}
\end{verbatim}
\section{Related Topics}\label{related-topics-45}
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api\#rating-value-transformation}{Rating
Value Transformation}
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-rating-type-selection}{Implementing
Rating Type Selection}
\href{/docs/7-2/frameworks/-/knowledge_base/f/rating-assets}{Rating
Assets}
\chapter{Flagging Inappropriate Asset
Content}\label{flagging-inappropriate-asset-content}
The asset framework supports a system for flagging inappropriate content
in apps. The steps here show you how to enable it in your app.
\begin{figure}
\centering
\includegraphics{./images/social-flags.png}
\caption{Users can flag objectionable content.}
\end{figure}
Follow these steps to enable content flagging in your app:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make sure your entity is
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{asset
enabled}.
\item
Choose a read-only view of the entity you want to enable flags on. You
can display flags in one of your app's views, or if you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer}{implemented
asset rendering} you can display it in the full content view in the
Asset Publisher app.
\item
In your JSP, include the \texttt{liferay-flags} taglib declaration:
\begin{verbatim}
<%@ taglib prefix="liferay-flags" uri="http://liferay.com/tld/flags" %>
\end{verbatim}
\item
Use
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}}
to get the entity's ID from the render request. Then use your
\texttt{-LocalServiceUtil} class to create an entity object:
\begin{verbatim}
<%
long entryId = ParamUtil.getLong(renderRequest, "entryId");
entry = EntryLocalServiceUtil.getEntry(entryId);
%>
\end{verbatim}
\item
Use the
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/taglibdocs/liferay-flags/flags.html}{\texttt{liferay-flags:flags}}
tag to add the flags component:
\begin{verbatim}
\end{verbatim}
The \texttt{reportedUserId} attribute specifies the ID of the user who
flagged the asset.
\end{enumerate}
\section{Related Topics}\label{related-topics-46}
\href{/docs/7-2/frameworks/-/knowledge_base/f/rating-assets}{Rating
Assets}
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api}{Social API}
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset
Framework}
\chapter{Configurable Applications}\label{configurable-applications}
Many applications must be configurable, whether by end users or
administrators. A configuration solution must support use cases ranging
from setting a location for a weather display to more complex cases like
settings for a mail or time sheet application.
The Portlet standard's portlet preferences API can be used for portlet
configuration, but it's intended for storing user preferences. This
limits its usefulness for enabling administrator configuration; plus it
can only be used with portlets. Instead, application developers tend to
create ad hoc configuration methods. But this isn't necessary.
Liferay DXP's configuration API is easy to use and is not limited to
portlets. When you define configuration options in a Java interface,
Liferay's configuration framework auto-generates a UI, sparing you the
trouble of developing an interface for your users to select
configuration options.
\noindent\hrulefill
\textbf{Note:} To see a working application configuration, deploy the
\texttt{configuration-action}
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/apps/configuration-action}{Blade
sample} and navigate to System Settings (\emph{Control Panel} →
\emph{Configuration} → \emph{System Settings}). In the Platorm section's
Third Party category, click the \emph{Message display configuration}
entry.
Add the \emph{Blade Message Portlet} to a page to test your
configuration choices.
\noindent\hrulefill
Complete these three high level tasks to integrate your application with
the configuration framework:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Provide a way to set configurations in the UI.
\item
Set the scope where the application is configured.
\item
Read configuration values in your business logic.
\end{enumerate}
\section{Using a Configuration
Interface}\label{using-a-configuration-interface}
You can take care of the first two steps by
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-configuration-interface}{Creating
A Configuration Interface}. This Java interface does a number of things:
\begin{itemize}
\item
Just by existing, it gives you a UI in \emph{System Settings}, so you
don't have to write one yourself. Score!
\item
It defines the configuration options that will appear in the UI.
\item
It defines the type \{\texttt{int}, \texttt{String}, etc.) of values
each configuration takes.
\item
It defines the scope of your configuration. Bonus in 7.0: if your
configuration is scoped to anything other than \texttt{SYSTEM}, you
get an additional UI generated for you in \emph{Instance Settings}.
More on scope in a minute.
\item
It categorizes your configuration screen so that it can be easily
found in \emph{System Settings} and \emph{Instance Settings}. If you
skip this the screen will be put in a default location.
\end{itemize}
A few things you need to know:
\begin{description}
\tightlist
\item[\textbf{Typed Configuration}]
The method described here uses \emph{typed} configuration. The
application configuration isn't just a list of key-value pairs. Values
can have types, like \texttt{Integer}, a list of \texttt{Strings}, a
URL, etc. You can even use your own types, although that's beyond the
scope of this tutorial. Typed configurations are easier to use than
untyped configurations, and they prevent many programmatic errors.
Configuration options should be programmatically explicit, so developers
can use autocomplete in modern IDEs to find out all configuration
options of a given application or one of its components.
\item[\textbf{Configuration Scope}]
Scope defines where a configuration value applies. Here are the most
common configuration scopes:
\end{description}
\begin{itemize}
\item
\texttt{SYSTEM}: Configuration values apply throughout the system.
\item
\texttt{COMPANY}: One set of configuration values is stored for each
virtual instance, so each instance can be configured individually.
\item
\texttt{GROUP}: Each group can be configured individually.
\item
\texttt{PORTLET\_INSTANCE}: this refers to apps that can be placed on
a page as a widget. Each widget can be configured individually.
\end{itemize}
\textbf{Configuration UIs} : When you create a configuration interface
of any sort, a UI is generated for you in \emph{System Settings}. If
your configuration is scoped to \texttt{COMPANY}, \texttt{GROUP}, or
\texttt{PORTLET\_INSTANCE}, an additional UI is generated in
\emph{Instance Settings}. Note that while \texttt{GROUP} and
\texttt{PORTLET\_INSTANCE} configurations appear in the Instance
Settings UI, they can only be used to set defaults for the current
instance. No corresponding UI is auto-generated to configure the app at
the Site or Portlet level.
\noindent\hrulefill
\textbf{Note:} An Instance Settings UI is not currently generated for
factory configurations. You can track the progress of this issue
\href{https://issues.liferay.com/browse/LPS-94490}{here}.
\noindent\hrulefill
\begin{description}
\tightlist
\item[\textbf{Default Configurations}]
Default values for any scoped configuration can be set at any wider
scope. For example, if your configuration is scoped to the
\texttt{GROUP}, you can set a system-wide default in \emph{System
Settings, an instance-wide default in }Instance Settings*, or both. Any
configuration at a narrower scope will always override a configuration
at a wider scope.
\end{description}
Read more about configuration scope
\href{/docs/7-2/user/-/knowledge_base/u/system-settings\#configuration-scope}{here}.
When you complete your configuration interface, you're done with steps 1
and 2 above.
\section{Reading Configuration
Values}\label{reading-configuration-values}
The final step is to make your app read the configuration values that
users enter. There are a number of ways to do that:
If your configuration is scoped to \texttt{COMPANY} or \texttt{GROUP}
you must use
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-scoped-configuration-values}{\texttt{ConfigurationProvider}}
This allows your app to read different configuration values from each
site, virtual instance, or whatever the configuration is scoped to.
If your configuration is scoped to \texttt{PORTLET\_INSTANCE}, you can
still use \texttt{ConfigurationProvider}, but using
\texttt{PortletDisplay} is simpler and more convenient. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-scoped-configuration-values\#accessing-the-portlet-instance-configuration-through-the-portletdisplay}{\texttt{PortletDisplay}}.
If you only want your app to be configurable at the \texttt{SYSTEM}
scope, you have a few options. \texttt{ConfigurationProvider} will work
fine, but there are alternatives that---since they don't need to query
multiple sources---can yield modest performance benefits. Which one you
use depends on what kind of class you're using to read configuration
values. Here are your options:
\begin{itemize}
\item
Read with an
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-an-mvc-portlet\#accessing-the-configuration-from-a-jsp}{MVC
portlet's JSP}
\item
With an
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-an-mvc-portlet\#accessing-the-configuration-from-the-portlet-class}{MVC
Portlet's Portlet Class}
\item
With any other
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-a-component}{Component
Class}
\end{itemize}
\section{Further Customization}\label{further-customization}
At this point you may be asking, ``But what if I don't \emph{like} the
auto-generated UI?'' Relax. There are a number of ways you can customize
it, or even suppress it entirely so you can put your own UI in its
place.
\begin{itemize}
\item
Implement the \texttt{ConfigurationFormRenderer}
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuration-form-renderer}{interface}
to customize the auto-generated UI in system settings.
\item
If you need more flexibility---perhaps your app needs multiple
configuration screens, or maybe you've already written a configuration
UI and just want to insert it without bothering to write a
configuration interface---implement the \texttt{ConfigurationScreen}
interface to implement your own.
\item
If you're using a configuration interface but you don't want a UI to
be generated---maybe you're using a \texttt{ConfigurationScreen}
implementation instead, or maybe you just want configuration to be
handled programatically or by
\href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{.config
file} ---you can
\href{/docs/frameworks/-/knowledge_base/7-2/customizing-the-system-settings-user-interface\#excluding-a-configuration-ui-from-system-settings}{just
leave it out}.
\item
If you want the UI to render only under certain circumstances, you can
write logic to
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-the-configuration-user-interface\#excluding-a-configuration-ui}{do
that, too}.
\end{itemize}
Enough conceptual stuff. You're ready to get started with some code. If
you already have an app that was configurable under an earlier version
of Liferay DXP, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/upgrading-a-legacy-app}{Upgrading
a Legacy App}.
\chapter{Creating A Configuration
Interface}\label{creating-a-configuration-interface}
First, you'll learn how to create a configuration with no scope
declaration. This automatically scopes your configuration to
\texttt{SYSTEM}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a Java interface to represent the configuration and its default
values. Using a Java interface allows for an advanced type system for
each configuration option. Here is the configuration interface for the
Liferay Forms application:
\begin{verbatim}
@Meta.OCD(
id = "com.liferay.dynamic.data.mapping.form.web.configuration.DDMFormWebConfiguration",
localization = "content/Language", name = "ddm-form-web-configuration-name"
)
public interface DDMFormWebConfiguration {
@Meta.AD(
deflt = "1", description = "autosave-interval-description",
name = "autosave-interval-name", required = false
)
public int autosaveInterval();
@Meta.AD(
deflt = "descriptive", name = "default-display-view",
optionLabels = {"Descriptive", "List"},
optionValues = {"descriptive", "list"}, required = false
)
public String defaultDisplayView();
}
\end{verbatim}
This defines two configuration options, the autosave interval (with a
default of one minute) and the default display view, which can be
descriptive or list, but defaults to descriptive. Here's what the two
Java annotations in the above snippet do:
\textbf{Meta.OCD:} Registers this class as a configuration with a
specific id. \textbf{The ID must be the fully qualified configuration
class name.}
\textbf{Meta.AD:} Specifies
\href{http://bnd.bndtools.org/chapters/210-metatype.html}{optional
metadata} about the field, such as whether it's a required field or if
it has a default value. Note that if you set a field as required and
don't specify a default value, the system administrator must specify a
value in order for your application to work properly. Use the
\texttt{deflt} property to specify a default value.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can dynamically populate select field options with the
[`ConfigurationFieldsOptionProvider` interface](/docs/7-2/frameworks/-/knowledge_base/f/dynamically-populating-select-list-fields-in-the-configuration-ui)
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
The fully-qualified name of the `Meta` class above is
`aQute.bnd.annotation.metatype.Meta`. For more information about this class and
the `Meta.OCD` and `Meta.AD` annotations, please refer to the
[bndtools documentation](http://bnd.bndtools.org/chapters/210-metatype.html).
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
To use the \texttt{Meta.OCD} and \texttt{Meta.AD} annotations in your
modules, you must
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{specify
a dependency} on the bnd library. We recommend using bnd version 3.
Here's an example of how to include this dependency in a Gradle
project:
\begin{verbatim}
dependencies {
compile group: "biz.aQute.bnd", name: "biz.aQute.bndlib", version: "3.1.0"
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} The annotations \texttt{@Meta.OCD} and \texttt{@Meta.AD}
are part of the bnd library, but as of OSGi standard version R6, they're
included in the OSGi core under the names
\texttt{@ObjectClassDefinition} and \texttt{@AttributeDefinition}. The
OSGi annotations can be used for simple cases like the one described in
this tutorial. However, a key difference between the two libraries is
that the bnd annotations are available at runtime, while the OSGi
annotations are not. Because runtime availability is necessary for some
of the Liferay-specific features described below, we recommend
defaulting to the bnd annotations.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Also Note:} Your project depends on a \texttt{-metatype:\ *}
declaration in its metadata. If you're in a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}(or otherwise applying the
\href{/docs/7-2/reference/-/knowledge_base/r/gradle-plugins}{workspace
plugin to your build}), it's added automatically at build time.
Otherwise, add it manually in your module's \texttt{bnd.bnd}. It's
required to provide information about your app's configuration options
so that a configuration UI can be generated.
\noindent\hrulefill
When you register a configuration interface, a UI is auto-generated for
it in \emph{System Settings} → \emph{Platform} → \emph{Third Party}.
That's the default location; read the next section to learn how to move
it somewhere more intuitive.
\chapter{Categorizing the
Configuration}\label{categorizing-the-configuration}
By default, the configuration UI for your app is generated in
\emph{System Settings} → \emph{Platform} → \emph{Third Party}. You
probably don't really want it there; by categorizing your configuration
you can place it somewhere intuitive and easy to find.
\noindent\hrulefill
\textbf{Note:} If you
\href{/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations}{scope}
your configuration so that a UI is generated in Instance Settings as
well, your categorization will apply to that UI also.
\noindent\hrulefill
You have two options: 1) locate your configuration UI in an existing
category and section, or 2) create your own.
Here are the default System Settings sections. All available categories
are nested beneath these sections:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Content and Data
\item
Platform
\item
Security
\item
Commerce
\item
Other
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} Sections appear if they contain at least one
configuration category. Categories appear if they contain at least one
configuration. The visible sections and categories depend on the
deployed modules.
\noindent\hrulefill
\section{Specifying a Configuration
Category}\label{specifying-a-configuration-category}
Specify the category for your UI by placing an
\texttt{@ExtendedObjectClassDefinition} annotation in your configuration
interface. This example, which appears right before the interface's
\texttt{@Meta.OCD} annotation, places the UI in the
\texttt{dynamic-data-mapping} category in the Content management
section:
\begin{verbatim}
@ExtendedObjectClassDefinition(
category = "dynamic-data-mapping",
scope = ExtendedObjectClassDefinition.Scope.GROUP
)
\end{verbatim}
This annotation does two things:
\begin{itemize}
\item
Specifies the \texttt{dynamic-data-mapping} category in the Content
Management section.
\item
Sets the scope of the configuration. You'll learn more about this
\href{/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations}{next}.
\end{itemize}
The fully qualified class name of the
\texttt{@ExtendedObjectClassDefinition} class is
\texttt{com.liferay.portal.configuration.metatype.annotations.ExtendedObjectClassDefinition}.
Note: The infrastructure used by System Settings assumes the
\texttt{configurationPid} is the same as the fully qualified class name
of the interface. If they don't match, it can't provide any information
through \texttt{ExtendedObjectClassConfiguration}.
The \texttt{@ExtendedObjectClassDefinition} annotation is distributed
through the \texttt{com.liferay.portal.configuration.metatype} module,
which you can
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{configure
as a dependency}.
\section{Creating New Sections and
Categories}\label{creating-new-sections-and-categories}
If you don't like the default sections and categories, you can create
your own by implementing the \texttt{ConfigurationCategory} interface.
Here's code that creates the \emph{Content and Data} section and the
\emph{Dynamic Data Mapping} category:
\begin{verbatim}
@Component(service = ConfigurationCategory.class)
public class DynamicDataMappingConfigurationCategory
implements ConfigurationCategory {
@Override
public String getCategoryIcon() {
return _CATEGORY_ICON;
}
@Override
public String getCategoryKey() {
return _CATEGORY_KEY;
}
@Override
public String getCategorySection() {
return _CATEGORY_SECTION;
}
private static final String _CATEGORY_ICON = "dynamic-data-mapping";
private static final String _CATEGORY_KEY = "dynamic-data-mapping";
private static final String _CATEGORY_SECTION = "content-and-data";
}
\end{verbatim}
The \texttt{getCategorySection} method returns the String with the new
section's key. Similarly, \texttt{getCategoryKey} returns the key for
the new category. Provide localized values for these keys in your
module's \texttt{src/main/resources/content/Language.properties} file.
\noindent\hrulefill
\textbf{Note:} the language keys for categories and sections must follow
a specific format. Prefix each section language key with
\texttt{category-section.} and each category language key with
\texttt{category.} For example:
\texttt{category-section.content-and-data=Content\ and\ Data}
\texttt{category.dynamic-data-mapping=Dynamic\ Data\ Mapping}
\noindent\hrulefill
Next you'll specify the scope of your application's configuration.
\chapter{Scoping Configurations}\label{scoping-configurations}
Here's how to scope a configuration:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Set the scope in the configuration interface.
\item
Enable the configuration for scoped retrieval by creating a
configuration bean declaration.
\end{enumerate}
\section{Step 1: Setting the Configuration
Scope}\label{step-1-setting-the-configuration-scope}
Use the \texttt{@ExtendedObjectClassDefinition} annotation to specify
the configuration's scope. The scope you choose must match how the
configuration object is retrieved through the
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-scoped-configuration-values}{configuration
provider}. Pass one of these valid scope options to
\texttt{@ExtendedObjectClassDefinition}:
\texttt{Scope.SYSTEM}: for system scope \texttt{Scope.COMPANY}: for
virtual instance scope \texttt{Scope.GROUP}: for site scope
\texttt{Scope.PORTLET\_INSTANCE}: for the portlet instance scope
Here is an example:
\begin{verbatim}
@ExtendedObjectClassDefinition(
category = "dynamic-data-mapping",
scope = ExtendedObjectClassDefinition.Scope.GROUP
)
@Meta.OCD(
id = "com.liferay.dynamic.data.mapping.form.web.configuration.
DDMFormWebConfiguration",
localization = "content/Language",
name = "ddm-form-web-configuration-name"
)
public interface DDMFormWebConfiguration {
\end{verbatim}
\section{Step 2: Enabling the Configuration for Scoped
Retrieval}\label{step-2-enabling-the-configuration-for-scoped-retrieval}
To create a configuration bean declaration:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Register the configuration class by implementing
\texttt{ConfigurationBeanDeclaration}.
\begin{verbatim}
@Component
public class JournalGroupServiceConfigurationBeanDeclaration
implements ConfigurationBeanDeclaration {
\end{verbatim}
\item
This class has one method that returns the class of the configuration
interface you created. It enables the system to keep track of
configuration changes as they happen, making requests for the
configuration very fast.
\begin{verbatim}
@Override
public Class> getConfigurationBeanClass() {
return JournalGroupServiceConfiguration.class;
}
\end{verbatim}
\end{enumerate}
That's all there is to it. Now the configuration is scoped and supports
scoped retrieval via \texttt{ConfigurationProvider}. See the next
section for details on retrieval.
\chapter{Reading Scoped Configuration
Values}\label{reading-scoped-configuration-values}
If your configuration is scoped to anything other than \texttt{SYSTEM},
you have two options for reading configuration values.
\begin{itemize}
\item
Use \texttt{ConfigurationProvider}. This works for any kind of
configuration, and is the only way to read configuration values at the
\texttt{COMPANY} and \texttt{GROUP} scopes.
\item
Use \texttt{PortletDisplay}. This is the recommended approach for
configurations at the \texttt{PORTLET\_INSTANCE} scope, but only works
at that scope.
\end{itemize}
\section{Using the Configuration
Provider}\label{using-the-configuration-provider}
When using the Configuration Provider, instead of receiving the
configuration directly, the class that wants to access it must
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Receive a \texttt{ConfigurationProvider} to obtain the configuration.
\item
Be registered with a \texttt{ConfigurationBeanDeclaration}.
\end{enumerate}
The tutorial on
\href{/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations}{scoping
configurations} demonstrates how to register the configuration with a
\texttt{ConfigurationBeanDeclaration}.
After registering with a \texttt{ConfigurationBeanDeclaration}, you're
ready to use a \texttt{ConfigurationProvider} to retrieve the scoped
configuration. Here's how you obtain a reference to it:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Here's the approach for components:
\begin{verbatim}
@Reference(unbind = "-")
protected void setConfigurationProvider(ConfigurationProvider configurationProvider) {
_configurationProvider = configurationProvider;
}
\end{verbatim}
\item
Here's the approach for Service Builder services:
\begin{verbatim}
@ServiceReference(type = ConfigurationProvider.class)
protected ConfigurationProvider configurationProvider;
\end{verbatim}
\item
For Spring beans, it is possible to use the same mechanism as for
Service Builder services (\texttt{@ServiceReference}).
\item
For anything else, call the same methods from the utility class,
\texttt{ConfigurationProviderUtil}. Be sure you call the utility
methods in contexts where the portal is guaranteed to be initialized
prior to the method call. This class is useful in the
\href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{scripting
console}, for example. Here's an example method that uses the utility
class. It comes from the export-import service, which is only called
during the import and export of content from a running portal:
\begin{verbatim}
protected boolean isValidateLayoutReferences() throws PortalException {
long companyId = CompanyThreadLocal.getCompanyId();
ExportImportServiceConfiguration exportImportServiceConfiguration =
ConfigurationProviderUtil.getCompanyConfiguration(
ExportImportServiceConfiguration.class, companyId);
return exportImportServiceConfiguration.validateLayoutReferences();
}
\end{verbatim}
\end{enumerate}
To retrieve the configuration, use one of the following methods of the
provider:
\begin{description}
\tightlist
\item[\texttt{getCompanyConfiguration()}]
Used when you want to support different configurations per virtual
instance. In this case, the configuration is usually entered by an admin
through \emph{Control Panel} → \emph{Configuration} → \emph{Instance
Settings}.
\item[\texttt{getGroupConfiguration()}]
Used when you want to support different configurations per site (or, if
desired, per page scope). Usually this configuration is specified by an
admin through the Configuration menu option in an app accessing through
the site administration menu. That UI is developed as a portlet
configuration view.
\item[\texttt{getPortletInstanceConfiguration()}]
Used to obtain the configuration for a specific portlet instance. Most
often you should not be using this directly. Use the convenience method
in \texttt{PortletDisplay} instead as shown below.
\item[\texttt{getSystemConfiguration}]
Used to obtain the configuration for the system scope. These settings
are specified by an admin via the System Settings application or with an
OSGi configuration file.
\end{description}
Here are a couple real world examples from Liferay's source code:
\begin{verbatim}
JournalGroupServiceConfiguration configuration =
configurationProvider.getGroupConfiguration(
JournalGroupServiceConfiguration.class, groupId);
MentionsGroupServiceConfiguration configuration =
_configurationProvider.getCompanyConfiguration(
MentionsGroupServiceConfiguration.class, entry.getCompanyId());
\end{verbatim}
Next, you'll learn a nifty way to to access a portlet instance
configuration from a JSP.
\section{\texorpdfstring{Accessing the Portlet Instance Configuration
Through the
\texttt{PortletDisplay}}{Accessing the Portlet Instance Configuration Through the PortletDisplay}}\label{accessing-the-portlet-instance-configuration-through-the-portletdisplay}
Often you must access portlet instance settings from a JSP or from a
Java class that isn't an OSGi component. To read the settings in these
cases, a method was added to \texttt{PortletDisplay}, which is available
as a request object. Here is an example of how to use it:
\begin{verbatim}
RSSPortletInstanceConfiguration rssPortletInstanceConfiguration =
portletDisplay.getPortletInstanceConfiguration(
RSSPortletInstanceConfiguration.class);
\end{verbatim}
As you can see, it knows how to find the values and returns a typed bean
containing them just by passing the configuration class.
\chapter{Reading Unscoped Configuration Values from an MVC
Portlet}\label{reading-unscoped-configuration-values-from-an-mvc-portlet}
If your configuration is scoped to \texttt{SYSTEM} or is unscoped (which
amounts to the same thing), you have a couple of options for reading
configuration values. There are two ways to do this:
\begin{itemize}
\item
Add a configuration to the request and read it from the view layer
(commonly a JSP).
\item
Read values directly from the portlet class.
\end{itemize}
This tutorial uses dummy code from a portlet we'll call the Example
Configuration Portlet. The import statements are included in the code
snippets so that you can see the fully qualified class names (FQCNs) of
all the classes that are used.
\section{Accessing the Configuration Object in the Portlet
Class}\label{accessing-the-configuration-object-in-the-portlet-class}
Whether you need the configuration values in the portlet class or the
JSPs, the first step is to get access to the configuration object in the
\texttt{*Portlet} class.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Imports first:
\begin{verbatim}
package com.liferay.docs.exampleconfig;
import java.io.IOException;
import java.util.Map;
import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;
import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
\end{verbatim}
\item
MVC Portlet classes are Component classes. If you have a Bean Portlet
or PortletMVC4Spring class, the configuration below goes in
\texttt{portlet.xml} and \texttt{liferay-portlet.xml}. To mate the
configuration with the Component, provide the
\texttt{configurationPid} property with the FQCN of the configuration
class.
\begin{verbatim}
@Component(
configurationPid = "com.liferay.docs.exampleconfig.ExampleConfiguration",
immediate = true,
property = {
"com.liferay.portlet.display-category=category.sample",
"com.liferay.portlet.instanceable=true",
"javax.portlet.security-role-ref=power-user,user",
"javax.portlet.init-param.template-path=/",
"javax.portlet.init-param.view-template=/view.jsp",
"javax.portlet.resource-bundle=content.Language"
},
service = Portlet.class
)
public class ExampleConfigPortlet extends MVCPortlet {
\end{verbatim}
Note that you can specify more than one configuration PID here by
enclosing the values in curly braces (\texttt{\{\}}) and placing
commas between each PID.
\item
Write an \texttt{activate} method annotated with \texttt{@Activate}
and \texttt{@Modified}. This ensures that the method is invoked when
the Component is started, and again whenever the configuration is
changed.
\begin{verbatim}
@Activate
@Modified
protected void activate(Map properties) {
_configuration = ConfigurableUtil.createConfigurable(
ExampleConfiguration.class, properties);
}
private volatile ExampleConfiguration _configuration;
\end{verbatim}
\end{enumerate}
A volatile field \texttt{\_configuration} is created by the
\texttt{createConfigurable} method. Now the field can be used to
retrieve configuration values or to set the values in the request, so
they can be retrieved in the application's JSPs.
\section{Accessing the Configuration from a
JSP}\label{accessing-the-configuration-from-a-jsp}
In the case of reading from a JSP, add the configuration object to the
request object so its values can be read from the JSPs that comprise the
application's view layer.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the configuration object to the request. Here's what it looks like
in a simple portlet's \texttt{doView} method:
\begin{verbatim}
@Override
public void doView(RenderRequest renderRequest,
RenderResponse renderResponse) throws IOException, PortletException {
renderRequest.setAttribute(
ExampleConfiguration.class.getName(), _configuration);
super.doView(renderRequest, renderResponse);
}
\end{verbatim}
The main difference between this example and the component class
covered in the
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-a-component}{next
section} is that this class is a portlet class and it sets the
configuration object as a request attribute in its \texttt{doView()}
method.
\item
Read configuration values from a JSP. First add these imports to the
top of your \texttt{view.jsp} file:
\begin{verbatim}
<%@ page import="com.liferay.docs.exampleconfig.ExampleConfiguration" %>
<%@ page import="com.liferay.portal.kernel.util.GetterUtil" %>
\end{verbatim}
\item
In the JSP, obtain the configuration object from the request object
and read the desired configuration value from it. Here's a
\texttt{view.jsp} file that does this:
\begin{verbatim}
<%@ include file="/init.jsp" %>
Hello from the Example Configuration portlet!
<%
ExampleConfiguration configuration = (ExampleConfiguration) GetterUtil.getObject(
renderRequest.getAttribute(ExampleConfiguration.class.getName()));
String favoriteColor = configuration.favoriteColor();
%>
Favorite color: <%= favoriteColor %>
properties) {
_formWebConfiguration = ConfigurableUtil.createConfigurable(
DDMFormWebConfiguration.class, properties);
}
private volatile DDMFormWebConfiguration _formWebConfiguration;
\end{verbatim}
The \texttt{activate()} method calls the method
\texttt{ConfigurableUtil.createConfigurable()} to convert a map of the
configuration's properties to a typed class, which is easier to
handle. The configuration is stored in a \texttt{volatile} field.
Don't forget to make it \texttt{volatile} to prevent thread safety
problems.
\item
Once the activate method is set up, retrieve particular properties
from the configuration wherever they're needed:
\begin{verbatim}
public void orderCar(String model) {
order("car", model, _configuration.favoriteColor());
}
\end{verbatim}
This is dummy code: don't try to find it in the Liferay source code.
The String configuration value of \texttt{favoriteColor} is passed to
the \texttt{order} method call, presumably so that whatever model car
is ordered gets ordered in the configured favorite color.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} The bnd library also provides a class called
\texttt{aQute.bnd.annotation.metatype.Configurable} with a
\texttt{createConfigurable()} method. You can use that instead of
Liferay's
\texttt{com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil}
without any problems. Liferay's developers created the
\texttt{ConfigurableUtil} class to improve the performance of bnd's
implementation, and it's used in internal code. Feel free to use
whichever method you prefer.
\noindent\hrulefill
With very few lines of code, you have a configurable application that
dynamically changes its configuration, has an auto-generated UI, and
uses a simple API to access the configuration.
\chapter{Customizing the Configuration User
Interface}\label{customizing-the-configuration-user-interface}
There are three ways to customize a configuration UI.
\begin{itemize}
\item
Provide a custom form for a configuration object. This modifies the
auto-generated UI.
\item
Write a completely custom configuration UI. This is useful especially
if you aren't using the Configuration Admin service or any of
Liferay's Configuration APIs.
\item
Exclude a configuration object. You'll want this option if you're
using a configuration interface but don't wan't a UI generated for
you.
\end{itemize}
\section{Providing Custom Configuration
Forms}\label{providing-custom-configuration-forms}
Customize your auto-generated UI by implementing the
\texttt{ConfigurationFormRender} interface. To write this interface, you
must refer to your configuration interface. For this example, refer to
this configuration interface from Liferay's Currency Converter
application:
\begin{verbatim}
@ExtendedObjectClassDefinition(category = "localization")
@Meta.OCD(
id = "com.liferay.currency.converter.web.configuration.CurrencyConverterConfiguration",
localization = "content/Language",
name = "currency-converter-configuration-name"
)
public interface CurrencyConverterConfiguration {
@Meta.AD(deflt = "GBP|CNY|EUR|JPY|USD", name = "symbols", required = false)
public String[] symbols();
}
\end{verbatim}
This example defines one configuration option, \texttt{symbols}, which
takes an array of values.
Implement \texttt{ConfigurationFormRenderer}'s three methods:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\texttt{getPid}: Return the configuration object's ID. This is defined
in the \texttt{id} property in the \texttt{*Configuration} class's
\texttt{@Meta.OCD} annotation.
\item
\texttt{getRequestParameters}: Read the parameters sent by the custom
form and put them in a Map whose keys should be the method names of
the Configuration interface.
\item
\texttt{render}: Render the custom form's fields, using your desired
method (for example, JSPs or another template mechanism). The
\texttt{\textless{}form\textgreater{}} tag itself is provided
automatically and shouldn't be included in the
\texttt{ConfigurationFormRenderer}.
\end{enumerate}
Here's a complete \texttt{ConfigurationFormRenderer} implementation:
\begin{verbatim}
@Component(immediate = true, service = ConfigurationFormRenderer.class)
public class CurrencyConverterConfigurationFormRenderer
implements ConfigurationFormRenderer {
@Override
public String getPid() {
return "com.liferay.currency.converter.web.configuration.CurrencyConverterConfiguration";
}
@Override
public void render(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String formHtml = " ";
PrintWriter writer = response.getWriter();
writer.print(formHtml);
}
@Override
public Map getRequestParameters(
HttpServletRequest request) {
Map params = new HashMap<>();
String[] mysymbols = ParamUtil.getParameterValues(request, "mysymbols");
params.put("symbols", mysymbols);
return params;
}
}
\end{verbatim}
The above example generates a custom rendering (HTML) for the form in
the \texttt{render()} method and reads the information entered in the
custom form in the \texttt{getRequestParameters()} method.
To see a complete demonstration, including JSP markup, read the
dedicated tutorial on creating a
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuration-form-renderer}{configuration
form renderer}.
\section{Creating a Completely Custom Configuration
UI}\label{creating-a-completely-custom-configuration-ui}
You get more flexibility if you create a completely custom UI using a
\texttt{ConfigurationScreen} implementation.
At a high level you must
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Write a Component that declares itself an implementation of the
\texttt{ConfigurationScreen} interface.
\item
Implement \texttt{ConfigurationScreen}'s methods.
\item
Create the UI by hand.
\end{enumerate}
Here's an example implementation:
\begin{verbatim}
@Component(immediate = true, service = ConfigurationScreen.class)
public class SampleConfigurationScreen implements ConfigurationScreen {
\end{verbatim}
First declare the class an implementation of
\texttt{ConfigurationScreen}.
\begin{verbatim}
@Override
public String getCategoryKey() {
return "third-party";
}
@Override
public String getKey() {
return "sample-configuration-screen";
}
@Override
public String getName(Locale locale) {
return "Sample Configuration Screen";
}
\end{verbatim}
Second, set the category key, the configuration entry's key, and its
localized name. This example puts the configuration entry, keyed
\texttt{sample-configuration-screen}, into the \texttt{third-party}
System Settings section. The String that appears in System Settings is
\emph{Sample Configuration Screen}.
\begin{verbatim}
@Override
public String getScope() {
return "system";
}
\end{verbatim}
Third, set the
\href{/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations}{configuration
scope}.
\begin{verbatim}
@Override
public void render(HttpServletRequest request, HttpServletResponse response)
throws IOException {
_jspRenderer.renderJSP( _servletContext, request, response,
"/sample_configuration_screen.jsp");
}
@Reference private JSPRenderer _jspRenderer;
@Reference(
target ="(osgi.web.symbolicname=com.liferay.currency.converter.web)",
unbind = "-")
private ServletContext _servletContext;
\end{verbatim}
The most important step is to write the \texttt{render} method. This
example relies on the \texttt{JSPRenderer} service to delegate rendering
to a JSP.
It's beyond the scope of this tutorial to write the JSP markup. A
separate tutorial will provide a complete demonstration of the
\texttt{ConfigurationScreen} and implementation and the JSP markup to
demonstrate its usage.
\section{Excluding a Configuration
UI}\label{excluding-a-configuration-ui}
If you don't want a UI to be generated for you, you have two options.
\begin{itemize}
\item
If you don't want a UI generated no matter what, use the
\texttt{generateUI} property.
\item
If you only want the UI to render under specific circumstances
(defined by logic you'll write yourself), use the configuration
visibility SPI.
\end{itemize}
\section{\texorpdfstring{Using
\texttt{generateUI}}{Using generateUI}}\label{using-generateui}
To turn off auto-generating at all scopes, include the
\texttt{ExtendedObjectClassDefinition} annotation property
\texttt{generateUI} in your configuration interface. The property
defaults to \texttt{true}; here is an example setting it to
\texttt{false}:
\begin{verbatim}
@ExtendedObjectClassDefinition(generateUI=false)
@Meta.OCD(
id = "com.foo.bar.LowLevelConfiguration",
)
public interface LowLevelConfiguration {
public String[] foo();
public String bar();
}
\end{verbatim}
Now no UI is auto-generated for this configuration. You can still manage
the configuration via a \texttt{ConfigurationScreen} implementation, a
\href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{.config
file}, or programmatically.
\section{Using the Configuration Visibility
SPI}\label{using-the-configuration-visibility-spi}
The configuration visibility SPI involves implementing a single
interface, \texttt{ConfigurationVisibilityController}. You can see the
whole interface
\href{https://github.com/liferay/liferay-portal/blob/48cd71b35a2d3b66e88f47685be7186cb7c52075/modules/apps/configuration-admin/configuration-admin-api/src/main/java/com/liferay/configuration/admin/display/ConfigurationVisibilityController.java}{here}.
To implement the interface, you must identify your configuration
interface using an \texttt{@Component} property, then write your own
logic for the interface's only method, \texttt{isVisible}. Here is a
sample implementation from Liferay's source code:
\begin{verbatim}
@Component(
immediate = true,
property = "configuration.pid=com.liferay.sharing.internal.configuration.SharingCompanyConfiguration",
service = ConfigurationVisibilityController.class
)
public class SharingCompanyConfigurationVisibilityController
implements ConfigurationVisibilityController {
@Override
public boolean isVisible(
ExtendedObjectClassDefinition.Scope scope, Serializable scopePK) {
SharingConfiguration systemSharingConfiguration =
_sharingConfigurationFactory.getSystemSharingConfiguration();
return systemSharingConfiguration.isEnabled();
}
@Reference
private SharingConfigurationFactory _sharingConfigurationFactory;
}
\end{verbatim}
Note that the property \texttt{configuration.pid} identifies the
configuration interface of the UI to be hidden. In this example, the
configuration UI only renders when
\texttt{systemSharingConfiguration.isEnabled} returns \texttt{true}.
\chapter{Configuration Form Renderer}\label{configuration-form-renderer}
To replace an application's auto-generated configuration screen with a
form built from scratch, you follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Use a \texttt{DisplayContext} class to transfer data between back-end
code and the desired JSP markup.
\item
Implement the \texttt{ConfigurationFormRenderer} interface.
\item
Render the configuration form. This tutorial demonstrates the use of a
JSP and the previously created \texttt{DisplayContext} class.
\end{enumerate}
A generalized discussion on System Settings UI customization is found in
a
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-the-configuration-user-interface}{separate
section}.
This article demonstrates replacing the configuration UI for the
\emph{Language Template} System Settings entry, found in \emph{Control
Panel} → \emph{Configuration} → \emph{System Settings} →
\emph{Localization} → \emph{Language Template}. The same steps apply
when replacing your custom application's auto-generated UI.
\begin{figure}
\centering
\includegraphics{./images/sys-settings-lang-template-default.png}
\caption{The auto-generated UI for the Language Template configuration
screen is sub-optimal. A select list with more human readable options is
preferable.}
\end{figure}
Specifically, the text input field labeled \emph{DDM Template Key} in
the auto-generated UI is replaced with a select list field type called
\emph{Language Selection Style}, populated with all possible DDM
Template Keys.
\section{\texorpdfstring{Creating a
\texttt{DisplayContext}}{Creating a DisplayContext}}\label{creating-a-displaycontext}
A \texttt{DisplayContext} class is a POJO that simplifies and minimizes
the use of Java logic in JSPs. Display context usage isn't required, but
it's a nice convention to follow. It's a kind of data transfer object,
where the \texttt{DisplayContext}'s setters are called from the Java
class providing the render logic (in this case the
\texttt{ConfigurationFormRenderer}'s \texttt{render} method), and the
getters are called from the JSP, removing the need for Java logic to be
written inside the JSP itself.
For this example, create a
\texttt{LanguageTemplateConfigurationDisplayContext} class with these
contents:
\begin{verbatim}
public class LanguageTemplateConfigurationDisplayContext {
public void addTemplateValue(
String templateKey, String templateDisplayName) {
_templateValues.add(new String[] {templateKey, templateDisplayName});
}
public String getCurrentTemplateName() {
return _currentTemplateName;
}
public String getFieldLabel() {
return _fieldLabel;
}
public List getTemplateValues() {
return _templateValues;
}
public void setCurrentTemplateName(String currentTemplateName) {
_currentTemplateName = currentTemplateName;
}
public void setFieldLabel(String fieldLabel) {
_fieldLabel = fieldLabel;
}
private String _currentTemplateName;
private String _fieldLabel;
private final List _templateValues = new ArrayList<>();
}
\end{verbatim}
Next implement the \texttt{ConfigurationFormRenderer}.
\section{\texorpdfstring{Implementing a
\texttt{ConfigurationFormRenderer}}{Implementing a ConfigurationFormRenderer}}\label{implementing-a-configurationformrenderer}
First create the component and class declarations. Set the
\texttt{service} property to \texttt{ConfigurationFormRenderer.class}:
\begin{verbatim}
@Component(
configurationPid = "com.liferay.site.navigation.language.web.configuration.SiteNavigationLanguageWebTemplateConfiguration",
immediate = true, service = ConfigurationFormRenderer.class
)
public class LanguageTemplateConfigurationFormRenderer
implements ConfigurationFormRenderer {
\end{verbatim}
Next, write an \texttt{activate} method (decorated with
\texttt{@Activate} and \texttt{@Modified}) to convert a map of the
configuration's properties to a typed class. The configuration is stored
in a volatile field. Don't forget to make it volatile to prevent thread
safety problems. See the article on
\href{/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-a-component}{reading
configuration values from a component class} for more information.
\begin{verbatim}
@Activate
@Modified
public void activate(Map properties) {
_siteNavigationLanguageWebTemplateConfiguration =
ConfigurableUtil.createConfigurable(
SiteNavigationLanguageWebTemplateConfiguration.class,
properties);
}
private volatile SiteNavigationLanguageWebTemplateConfiguration
_siteNavigationLanguageWebTemplateConfiguration;
\end{verbatim}
Next override the \texttt{getPid} and \texttt{getRequestParameters}
methods:
\begin{verbatim}
@Override
public String getPid() {
return "com.liferay.site.navigation.language.web.configuration." +
"SiteNavigationLanguageWebTemplateConfiguration";
}
\end{verbatim}
Return the full configuration ID, as specified in the
\texttt{*Configuration} class's \texttt{@Meta.OCD} annotation.
\begin{verbatim}
@Override
public Map getRequestParameters(
HttpServletRequest request) {
Map params = new HashMap<>();
String ddmTemplateKey = ParamUtil.getString(request, "ddmTemplateKey");
params.put("ddmTemplateKey", ddmTemplateKey);
return params;
}
\end{verbatim}
In the \texttt{getRequestParameters} method, map the parameters sent by
the custom form (obtained from the request) to the keys of the fields in
the Configuration interface.
Provide the render logic via the overridden \texttt{render} method. The
rendering approach demonstrated here uses a JSP. Recall that it's backed
by a \texttt{DisplayContext} class set into the request object. The
values set from this \texttt{render} method are available in the JSP via
the \texttt{DisplayContext} object's getters.
Loop through the DDM Template Keys for the given \texttt{groupId} and
set them into the display context with the \texttt{addTemplateKey}
method. Then set the other necessary values that the JSP needs. In this
case, set the title, the field label, and the redirect URL. Finally,
call \texttt{renderJSP} and pass in the \texttt{servletContext},
request, response, and the path to the JSP:
\begin{verbatim}
@Override
public void render(HttpServletRequest request, HttpServletResponse response)
throws IOException {
Locale locale = request.getLocale();
LanguageTemplateConfigurationDisplayContext
languageTemplateConfigurationDisplayContext =
new LanguageTemplateConfigurationDisplayContext();
languageTemplateConfigurationDisplayContext.setCurrentTemplateName(
_siteNavigationLanguageWebTemplateConfiguration.ddmTemplateKey());
long groupId = 0;
long companyId = _portal.getCompanyId(actionRequest);
Group group = _groupLocalService.fetchCompanyGroup(companyId);
if (group != null) {
groupId = group.getGroupId();
}
List ddmTemplates = _ddmTemplateLocalService.getTemplates(
groupId, _portal.getClassNameId(LanguageEntry.class));
for (DDMTemplate ddmTemplate : ddmTemplates) {
languageTemplateConfigurationDisplayContext.addTemplateValue(
ddmTemplate.getTemplateKey(), ddmTemplate.getName(locale));
}
languageTemplateConfigurationDisplayContext.setFieldLabel(
LanguageUtil.get(
ResourceBundleUtil.getBundle(
locale, LanguageTemplateConfigurationFormRenderer.class),
"language-selection-style"));
request.setAttribute(
LanguageTemplateConfigurationDisplayContext.class.getName(),
languageTemplateConfigurationDisplayContext);
_jspRenderer.renderJSP(
_servletContext, request, response,
"/configuration/site_navigation_language_web_template.jsp");
}
\end{verbatim}
Specify the required service references at the bottom of the class. Be
careful to target the proper servlet context, passing the
\texttt{bundle-SymbolicName} of the module (found in its
\texttt{bnd.bnd} file) into the \texttt{osgi.web.symbolicname} property
of the reference target:
\begin{verbatim}
@Reference
private DDMTemplateLocalService _ddmTemplateLocalService;
@Reference
private GroupLocalService _groupLocalService;
@Reference
private JSPRenderer _jspRenderer;
@Reference
private Portal _portal;
@Reference(
target = "(osgi.web.symbolicname=com.liferay.site.navigation.language.web)",
unbind = "-"
)
private ServletContext _servletContext;
\end{verbatim}
Once the configuration form renderer is implemented, you can write the
JSP markup for the form.
\section{Writing the JSP Markup}\label{writing-the-jsp-markup}
Now write the JSP:
\begin{verbatim}
<%@ include file="/init.jsp" %>
<%
LanguageTemplateConfigurationDisplayContext
languageTemplateConfigurationDisplayContext = (LanguageTemplateConfigurationDisplayContext)request.getAttribute(LanguageTemplateConfigurationDisplayContext.class.getName());
Admin: Instance Settings String currentTemplateName = languageTemplateConfigurationDisplayContext.getCurrentTemplateName();
%>
<%
for (String[] templateValue : languageTemplateConfigurationDisplayContext.getTemplateValues()) {
%>
<%
}
%>
\end{verbatim}
The opening scriptlet gets the display context object from the request
so that all its getters are invoked whenever information from the
back-end is required. Right away, the \texttt{getCurrentTemplateName}
method is called, since the current template name is needed for the
first option's \texttt{ddmTemplateKey} display value as soon as the form
is rendered. This happens in the
\texttt{\textless{}aui:select\textgreater{}} tag. There's just a bit of
logic used to create an option for each of the available DDM templates
that can be chosen.
So what does this example look like when all is said and done?
\begin{figure}
\centering
\includegraphics{./images/sys-settings-lang-template-custom.png}
\caption{A select list provides a more user friendly configuration
experience than a text field.}
\end{figure}
Now, administrators encountering the Language Template entry in System
Settings won't be handicapped by not knowing the available DDM Template
Keys. Providing the available values in a select field wildly enhances
the user experience.
\chapter{Using DDM Form Annotations in Configuration
Forms}\label{using-ddm-form-annotations-in-configuration-forms}
The auto-generated configuration form you get by just creating a
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-configuration-interface}{configuration
interface} can be too simplistic for some configurations. To enhance it,
use the Dynamic Data Mapping (DDM) Form Annotations.
To use DDM Annotations in configuration forms,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Configure the module dependencies.
\item
Write a \texttt{ConfigurationForm} class, including just the fields
that you want to leverage the enhanced forms capability. This is
similar to the configuration interface, but with field annotations
from the Liferay
\href{https://github.com/liferay/liferay-portal/tree/7.2.0-ga1/modules/apps/dynamic-data-mapping/dynamic-data-mapping-api/src/main/java/com/liferay/dynamic/data/mapping/annotations}{Dynamic
Data Mapping API} rather than the bndtools metatype specification. The
fields here must match fields defined in the configuration interface.
\item
Implement a
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/configuration-admin/configuration-admin-api/src/main/java/com/liferay/configuration/admin/definition/ConfigurationDDMFormDeclaration.java}{\texttt{ConfigurationDDMFormDeclaration}}
to mark your configuration as having a \texttt{ConfigurationForm}.
\end{enumerate}
This article assumes you already have an auto-generated configuration
UI.
Note that the example code here splits up the Configuration interface
and the Configuration Form interface. If you'd rather mash these
together into one class, you can. This approach might make sense if you
have a small number of fields in your configuration and a simple form to
create. If you have numerous configuration fields and/or a complex form
to create, or if you're taking an existing configuration and extending
it to use the DDM Form annotations, you can consider separating the
classes, as shown here.
\section{Step 1: Declare the
Dependencies}\label{step-1-declare-the-dependencies}
In the \texttt{build.gradle} file, add \texttt{compileOnly} dependencies
on the \texttt{dynamic-data-mapping-api} and
\texttt{configuration-admin-api} module artifacts:
\begin{verbatim}
compileOnly group: "com.liferay", name: "com.liferay.dynamic.data.mapping.api", version: "5.2.0"
compileOnly group: "com.liferay", name: "com.liferay.configuration.admin.api", version: "2.0.2"
\end{verbatim}
\section{Step 2: Write the Configuration
Form}\label{step-2-write-the-configuration-form}
This step requires annotating the class with \texttt{@DDMForm} to set up
the form, and annotating each method with \texttt{@DDMFormField}. Begin
by creating the class body, annotating each configuration field
(interface method) with \texttt{@DDMFormField}:
\begin{verbatim}
public interface MyFooConfigurationForm {
@DDMFormField(
label = "%label-key-for-field-1",
tip = "%description-key-for-field-1",
properties = {
"placeholder=%enter-a-value",
"tooltip=%some-tooltip-text"
}
)
public String[] textArrayValues();
@DDMFormField(
label = "%date",
tip = "%date-description",
type = "date")
public String date();
@DDMFormField(
label = "%select",
optionLabels = {"%foo", "%bar"},
optionValues = {"foo", "bar"},
type = "select")
public String select();
@DDMFormField(
label = "%numeric",
properties = {
"placeholder=%milliseconds",
"tooltip=%enter-an-integer-between-1000-and-30000"
},
validationErrorMessage = "%please-enter-an-integer-between-1000-and-30000-milliseconds",
validationExpression = "(numeric >= 1000) && (numeric <= 30000)",
type = "numeric")
public String numeric();
@DDMFormField(
label = "%checkbox",
properties = "showAsSwitcher=true")
public boolean checkbox();
}
\end{verbatim}
Once the field annotations are in place, lay out the form itself, right
above the class declaration. This example shows the layout of the
\texttt{UserFileUploadsConfigurationForm}, so that you can see the
resulting form via the below screenshot:
\begin{verbatim}
@DDMForm
@DDMFormLayout(
paginationMode = com.liferay.dynamic.data.mapping.model.DDMFormLayout.SINGLE_PAGE_MODE,
value = {
@DDMFormLayoutPage(
{
@DDMFormLayoutRow(
{
@DDMFormLayoutColumn(
size = 12,
value = {
"imageCheckToken", "imageDefaultUseInitials",
"imageMaxSize"
}
)
}
),
@DDMFormLayoutRow(
{
@DDMFormLayoutColumn(
size = 6, value = "imageMaxHeight"
),
@DDMFormLayoutColumn(size = 6, value = "imageMaxWidth")
}
)
}
)
}
)
public interface MyFooConfigurationForm {
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/configuration-ddm-form.png}
\caption{The DDM annotations are used to lay out this configuration
form.}
\end{figure}
Next, you must make sure the configuration framework knows about your
slick form.
\section{Step 3: Write the Form
Declaration}\label{step-3-write-the-form-declaration}
Create a new implementation of \texttt{ConfigurationDDMFormDeclaration}
to register your new configuration form class:
\begin{verbatim}
package com.liferay.docs.my.foo.configuration.definition;
import com.liferay.configuration.admin.definition.ConfigurationDDMFormDeclaration;
import org.osgi.service.component.annotations.Component;
...
@Component(
immediate = true,
property = "configurationPid=com.liferay.docs.my.foo.configuration.MyFooConfiguration",
service = ConfigurationDDMFormDeclaration.class
)
public class MyFooConfigurationDDMFormDeclaration
implements ConfigurationDDMFormDeclaration {
@Override
public Class> getDDMFormClass() {
return MyFooConfigurationForm.class;
}
}
\end{verbatim}
The \texttt{configurationPid} must match the fully qualified class name
of the configuration interface.
Now your configuration class is backed by the form-building power of
Liferay's native \href{/docs/7-2/user/-/knowledge_base/u/forms}{Forms
application}.
To see how this is done for one of Liferay's own configurations, check
out all of the configuration classes for the User Images configuration
(Control Panel → Configuration → System Settings → User Images):
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/users-admin/users-admin-api/src/main/java/com/liferay/users/admin/configuration/definition/UserFileUploadsConfigurationForm.java}{\texttt{UserFileUploadsConfigurationForm}}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/users-admin/users-admin-api/src/main/java/com/liferay/users/admin/configuration/UserFileUploadsConfiguration.java}{\texttt{UserFileUploadsConfiguration.java}}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/users-admin/users-admin-web/src/main/java/com/liferay/users/admin/web/internal/configuration/definition/UserFileUploadsConfigurationBeanDeclaration.java}{\texttt{UserFileUploadsConfigurationBeanDeclaration.java}}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/users-admin/users-admin-web/src/main/java/com/liferay/users/admin/web/internal/configuration/definition/UserFileUploadsConfigurationDDMFormDeclaration.java}{\texttt{UserFileUploadsConfigurationDDMFormDeclaration.java}}
\chapter{Upgrading a Legacy App}\label{upgrading-a-legacy-app}
If you have an app that was made configurable under an earlier version
of Liferay DXP, you can upgrade without having to reconfigure any of
your app's instances.
If you have an app that was configurable using the mechanisms of Liferay
Portal 6.2 and before, refer to
\href{/docs/7-0/tutorials/-/knowledge_base/t/transitioning-from-portlet-preferences-to-the-configuration-api}{Transitioning
from Portlet Preferences to the Configuration API}.
If you have an app with a configuration interface scoped to anything
other than \texttt{SYSTEM} and a custom UI for saving configuration
values to \texttt{PortletPreferences}, you have two options:
\begin{itemize}
\item
Keep using your custom UI. Deactivate the auto-generated UI in
\emph{Instance Settings} by setting the scope in your configuration
interface to \texttt{SYSTEM}. This is quick and easy, but won't make
your code easier to maintain in the long term.
For other ways to disable the auto-generated UI, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-the-configuration-user-interface\#excluding-a-configuration-ui}{Excluding
a Configuration UI}
\item
Write an Upgrade Process to convert your configuration values in
\texttt{PortletPreferences} to an instance-scoped OSGi configuration,
using the \texttt{saveCompanyConfiguration} method in the
\texttt{ConfigurationProvider} interface.
\end{itemize}
\noindent\hrulefill
You don't have to use \texttt{saveCompanyConfiguration}, but doing so
meets all the necessary requirements for an upgrade process: it must be
a factory instance with a factory PID of
\texttt{Unknown\ macro:{[}base-pid{]}.scoped}, and it must contain a
\texttt{companyId} property.
\noindent\hrulefill
Then remove your custom UI. If you're reading configuration values using
\texttt{ConfigurationProvider}'s \texttt{getCompanyConfiguration}
method, the auto-generated UI picks up where you left off, with no need
to reconfigure anything.
\chapter{Dynamically Populating Select List Fields in the Configuration
UI}\label{dynamically-populating-select-list-fields-in-the-configuration-ui}
You've always been able to provide a select list for your configuration
options by entering each label and value directly in the
\texttt{@Meta.AD} annotation of the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-configuration-interface}{Configuration
interface}.
\begin{verbatim}
@Meta.AD(
deflt = "enabled-with-warning", name = "csv-export",
optionLabels = {"enabled", "enabled-with-warning", "disabled"},
optionValues = {"enabled", "enabled-with-warning", "disabled"},
required = false
)
public String csvExport();
\end{verbatim}
Now, thanks to the
\href{https://docs.liferay.com/dxp/apps/configuration-admin/latest/javadocs/com/liferay/configuration/admin/definition/ConfigurationFieldOptionsProvider.html}{\texttt{ConfigurationFieldOptionsProvider}
interface}, you can populate select list configurations dynamically,
using custom logic.
Follow these steps to populate the select list fields dynamically in
your configuration UI:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use an \texttt{@Component} annotation to register the
\texttt{ConfigurationFieldOptionsProvider.class} service and include
two properties:
\texttt{configuration.field.name}: The name of the attribute in the
configuration interface
\texttt{configuration.pid}: The ID of the corresponding configuration
interface (usually the fully qualified class name)
For example,
\begin{verbatim}
@Component(
property = {
"configuration.field.name=enabledClassNames",
"configuration.pid=com.liferay.asset.auto.tagger.google.cloud.natural.language.internal.configuration.GCloudNaturalLanguageAssetAutoTaggerCompanyConfiguration",
"configuration.pid=com.liferay.asset.auto.tagger.opennlp.internal.configuration.OpenNLPDocumentAssetAutoTaggerCompanyConfiguration"
},
service = ConfigurationFieldOptionsProvider.class
)
\end{verbatim}
\item
Implement the \texttt{ConfigurationFieldOptionsProvider} interface:
\begin{verbatim}
public class MyConfigurationFieldOptionsProvider implements
ConfigurationFieldOptionsProvider {
..
}
\end{verbatim}
\item
The \texttt{getOptions} method returns a list of \texttt{Option}s
consisting of the label and value fields. The labels provided here are
translated to \texttt{optionLabels}, and the values as
\texttt{optionValues}, in the configuration interface.
\begin{verbatim}
public List getOptions() {
List> assetRendererFactories =
AssetRendererFactoryRegistryUtil.getAssetRendererFactories(
CompanyThreadLocal.getCompanyId());
Stream> stream =
assetRendererFactories.stream();
return stream.filter(
assetRendererFactory -> {
TextExtractor textExtractor =
_textExtractorTracker.getTextExtractor(
assetRendererFactory.getClassName());
return textExtractor != null;
}
).map(
assetRendererFactory -> new Option() {
@Override
public String getLabel(Locale locale) {
return assetRendererFactory.getTypeName(locale);
}
@Override
public String getValue() {
return assetRendererFactory.getClassName();
}
}
).collect(
Collectors.toList()
);
}
\end{verbatim}
This code gets a list of \texttt{AssetRendererFactory} objects and
iterates through the list, populating a new list of \texttt{Option}s,
using the asset's type name as the label and the class name as the
value. It comes from the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/asset/asset-auto-tagger-service/src/main/java/com/liferay/asset/auto/tagger/internal/configuration/admin/definition/EnabledClassNamesConfigurationFieldOptionsProvider.java}{\texttt{EnabledClassNamesConfigurationFieldOptionsProvider}},
which populates the configuration field labeled \emph{Enable Google
Cloud Natural Language Text Auto Tagging For} with all the asset types
that have registered a \texttt{TextExtractor}.
\begin{figure}
\centering
\includegraphics{./images/configuration-field-options-provider.png}
\caption{The select list in the Google Cloud Natural Language Text
Auto Tagging entry is populated programmatically, using the
\texttt{ConfigurationFieldOptionsProvider}.}
\end{figure}
\end{enumerate}
The \texttt{ConfigurationFieldOptionsProvider} allows you to populate
select lists with configuration options defined by your custom logic.
\chapter{Content Publication
Management}\label{content-publication-management}
Managing content publication is primarily controlled by two frameworks:
\begin{itemize}
\tightlist
\item
\hyperref[exportimport]{Export/Import}
\item
\hyperref[staging]{Staging}
\end{itemize}
These features give you the power to plan page publication and manage
content. You can leverage the APIs offered by these frameworks to manage
your app's publication process.
The Export/Import and Staging frameworks are closely tied together. They
both implement the same interfaces and share the same extension points.
By implementing one of these frameworks in your app, you automatically
leverage the other. There are a few simple configurations that can be
set to customize them separately. You'll learn about this later.
Export/Import can be viewed as the base feature with Staging built on
top of it (although they're implemented together). You can visit the
\href{/docs/7-2/frameworks/-/knowledge_base/f/export-import}{Export/Import}
framework's articles for the base APIs that both it and the Staging
frameworks share. You must implement these to implement Staging.
Reference the
\href{/docs/7-2/frameworks/-/knowledge_base/f/staging}{Staging}
framework's articles for additional configuration pertaining only to it.
Here are a few of the things you can do with the Export/Import and
Staging APIs.
\section{Export/Import}\label{exportimport}
The Export/Import feature adds another dimension to your application by
providing a framework for producing reusable content and importing
content from other places. By creating
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-archive-lar-file}{LAR
files (Liferay ARchive)}, you can export your data, import it to another
system, or even use this feature to back up your content.
\begin{figure}
\centering
\includegraphics{./images/export-import-preview.png}
\caption{Leveraging the Export/Import feature in your app is useful for
sharing content.}
\end{figure}
Export/Import is a default feature for many of Liferay DXP's
out-of-the-box apps. It offers an intuitive GUI for managing
export/import processes and tracking the history of previous
export/imports. You can also easily pick subsets of data to export based
on content type, date range, and configurations. Importing content is
done using a modern drag-and-drop interface or by selecting the LAR file
from your file system.
\section{Staging}\label{staging}
Staging gives you a test environment where you can modify your site and
test different configurations without changing your live site. When you
implement Staging in your app, its content can be tracked by the staged
environment, allowing users to stage your app's data before releasing it
to the world.
Here's an example of some functionality you can add to your app with
Staging's APIs:
\begin{itemize}
\tightlist
\item
Local Staging environment tracking
\item
Remote Staging environment tracking
\item
Single asset publishing
\item
Content tracking on page variations
\end{itemize}
Continue on to learn more about the frameworks that bring content
publication management to life!
\chapter{Export/Import}\label{exportimport-1}
The Export/Import feature exports content from the portal and imports
external content into the portal. Your application is much more site
administrator-friendly if users can export/import your application's
assets. For example, if you want to export your application's assets to
use on another installation or you must clear its data but save a copy,
you can implement the export feature. Implementing the import feature
lets you bring your assets/data back into your application.
Here's what you'll learn to do with the Export/Import framework:
\begin{itemize}
\tightlist
\item
Create Staged Models
\item
Develop Portlet Data Handlers
\item
Develop Staged Model Data Handlers
\item
Provide entity-specific local services for Export/Import framework
\item
Listen to export/import events
\item
Initiate new export/import processes programmatically
\end{itemize}
\section{Staged Models}\label{staged-models}
To track an entity of an application with the Export/Import framework,
you must implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/StagedModel.html}{\texttt{StagedModel}}
interface in the app's model classes. It provides the behavior contract
for entities during the Export/Import and Staging processes. There are
two ways to create staged models for your application's entities:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/generating-staged-models-using-service-builder}{Generate
them using Service Builder}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-staged-models-manually}{Implement
the appropriate interfaces manually}
\end{itemize}
Using Service Builder to generate your staged models is the easiest way
to create staged models for your app. You define the necessary columns
in your \texttt{service.xml} file and set the \texttt{uuid} attribute to
\texttt{true}. Then you run Service Builder, which generates the
required code for your new staged models.
Implementing the necessary staged model logic \emph{manually} should be
done if you \textbf{don't} want to extend your model with special
attributes only required to generate Staging logic (i.e., not needed by
your business logic). In this case, you should adapt your business logic
to meet the Staging framework's needs.
See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-staged-models}{Developing
Staged Models} section for more information on the Staged Model
architecture.
\section{Data Handlers}\label{data-handlers}
You must implement Data Handlers to use the Export/Import framework to
process
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-archive-lar-file}{LAR
files} in your application. There are two types of data handlers:
\begin{itemize}
\tightlist
\item
Portlet Data Handlers
\item
Staged Model Data Handlers
\end{itemize}
A Portlet Data Handler imports/exports portlet specific data to a LAR
file. These classes query and coordinate between staged model data
handlers. They also configure the Export/Import and Staging UI options.
A Staged Model Data Handler supplies information about a staged model
(entity) to the Export/Import framework, defining a display name for the
UI, deleting an entity, etc. It also exports referenced content.
Visit the
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-data-handlers}{Developing
Data Handlers} section for more information.
\section{Provide Entity Specific Local
Services}\label{provide-entity-specific-local-services}
When creating data handlers, you must leverage your app's local services
to perform Export/Import and Staging related tasks for its entities.
When these frameworks operate on entities (i.e., staged models), it
often cannot manage important information from the entity's local
services alone; instead, you're forced to reinvent basic functionality
so the framework can access it. This is caused by services not sharing a
common ancestor (i.e., interface or base class).
The \emph{Staged Model Repository} framework removes this barrier by
linking an app's staged model to a local service.
\begin{figure}
\centering
\includegraphics{./images/staged-model-repository.png}
\caption{Staged Model Repositories provide a Staging-specific layer of
functionality for your local services.}
\end{figure}
This lets the Staging framework call a staged model repository
independently based on the entity being processed. This gives you access
to entity-specific methods tailored specifically for the staged model
data you're handling.
\section{Export/Import Event
Listeners}\label{exportimport-event-listeners}
The \texttt{ExportImportLifecycleListener} framework is for listening
for certain staging or export/import events (like export successes and
import failures) during the publication process so you can take some
action. You can also listen for processes comprised of many events and
take action when these processes are initiated. For example, you can
listen for when
\begin{itemize}
\tightlist
\item
Staging has started
\item
A portlet export has failed
\item
An entity export has succeeded
\end{itemize}
After an event is triggered, you can take an action like these:
\begin{itemize}
\tightlist
\item
Print information about the event to your console
\item
When an import process has completed, clear the cache.
\end{itemize}
For a complete list of events you can listen for, see
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lifecycle/ExportImportLifecycleConstants.html}{\texttt{ExportImportLifecycleConstants}}.
You must extend one of the two Base listener classes:
\begin{itemize}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lifecycle/BaseExportImportLifecycleListener.html}{\texttt{BaseExportImportLifecycleListener}}:
listens for specific \emph{events} during a lifecycle. For example, if
a layout export fails, you might take some action.
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lifecycle/BaseProcessExportImportLifecycleListener.html}{\texttt{BaseProcessExportImportLifecycleListener}}:
listens for \emph{processes} during a lifecycle. A process usually
consists of many individual events. For example, if a site publication
fails, you might take some action. Methods provided by this base class
are only run once when the desired process action occurs.
\end{itemize}
What's the difference between events and processes?
\textbf{Events:} particular actions that occur during processing
(example event listener:
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/exportimport/lifecycle/CacheExportImportLifecycleListener.html}{\texttt{CacheExportImportLifecycleListener}}).
\textbf{Processes:} longer running groups of events (example process
listener:
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/exportimport/lifecycle/ExportImportProcessCallbackLifecycleListener.html}{\texttt{ExportImportProcessCallbackLifecycleListener}}).
Use the listener type that is most appropriate for your use case.
\section{Export/Import Processes}\label{exportimport-processes}
You can start the process programmatically instead of through the UI.
This lets you provide new interfaces or mimic the functionality of these
features in your own application.
To initiate an export/import or staging process, you must pass in an
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/model/ExportImportConfiguration.html}{\texttt{ExportImportConfiguration}}
object. This object encapsulates many parameters and settings that are
required while the export/import is running. Having one single object
with all your necessary data makes executing these frameworks quick and
easy.
For example, when implementing export, you must call services offered by
the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/service/ExportImportService.html}{\texttt{ExportImportService}}
interface. All the methods in this interface require an
\texttt{ExportImportConfiguration} object. You can generate these
configuration objects, so you can easily pass them in your service
methods.
There are three factory classes that are useful to create an
\texttt{ExportImportConfiguration} object:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/configuration/ExportImportConfigurationSettingsMapFactory.html}{\texttt{ExportImportConfigurationSettingsMapFactory}}:
provides many \texttt{build} methods to create settings maps for
various scenarios like importing, exporting, and publishing layouts
and portlets. For example, you can reference
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/service/impl/UserGroupLocalServiceImpl.html\#exportLayouts-long-java.util.Map-}{\texttt{UserGroupLocalServiceImpl.exportLayouts(...)}}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/service/impl/GroupLocalServiceImpl.html\#addDefaultGuestPublicLayoutsByLAR-com.liferay.portal.kernel.model.Group-java.io.File-}{\texttt{GroupLocalServiceImpl.addDefaultGuestPublicLayoutsByLAR(...)}}.
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/configuration/ExportImportConfigurationFactory.html}{\texttt{ExportImportConfigurationFactory}}:
This factory builds \texttt{ExportImportConfiguration} objects used
for default local/remote publishing.
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/configuration/ExportImportConfigurationParameterMapFactory.html}{\texttt{ExportImportConfigurationParameterMapFactory}}:
This factory builds parameter maps, which are required during
export/import and publishing.
\end{itemize}
There are two important service interfaces that primarily use
\texttt{ExportImportConfiguration} objects for exporting, importing, and
staging:
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/service/ExportImportLocalService.html}{\texttt{ExportImportLocalService}}
and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/service/StagingLocalService.html}{\texttt{StagingLocalService}}.
\noindent\hrulefill
\textbf{Note:} If you're not calling the export/import or staging
service methods from an OSGi module, you should not use the interface.
The Liferay OSGi container automatically handles interface referencing,
which is why using the interface is permitted for modules. If you're
calling export/import or staging service methods outside of a module,
you should use their service Util classes (e.g.,
\texttt{ExportImportLocalServiceUtil}).
\noindent\hrulefill
It's also important to know that \texttt{ExportImportConfiguration} is a
Liferay DXP entity, similar to \texttt{User} or \texttt{Group}. This
means that the \texttt{ExportImportConfiguration} framework offers local
and remote services, models, persistence classes, and more.
\chapter{Developing Staged Models}\label{developing-staged-models}
To track an entity of an application with the Export/Import framework,
you must implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/StagedModel.html}{\texttt{StagedModel}}
interface in the app's model classes. It provides the behavior contract
for entities during the Staging process. For example, the Bookmarks
application manages
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/bookmarks/model/BookmarksEntry.html}{\texttt{BookmarksEntry}}s
and
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/bookmarks/model/BookmarksFolder.html}{\texttt{BookmarksFolder}}s,
and both implement the \texttt{StagedModel} interface. Once you've
configured your staged models, you can create staged model data
handlers, which supply information about a staged model (entity) and its
referenced content to the Export/Import and Staging frameworks. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-data-handlers}{Developing
Data Handlers} section for more information.
There are two ways to create staged models for your application's
entities:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/generating-staged-models-using-service-builder}{Using
Service Builder to generate the required Staging implementations}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-staged-models-manually}{Implementing
the required Staging interfaces manually}
\end{itemize}
You can follow step-by-step procedures for creating staged models for
your entities by visiting their respective articles.
Continue on to learn more about Staged Models!
\section{Staged Model Interfaces}\label{staged-model-interfaces}
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/StagedModel.html}{\texttt{StagedModel}}
interface must be implemented by your app's model classes, but this is
typically done through inheritance by implementing one of the interfaces
that extend the base interface:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/StagedAuditedModel.html}{\texttt{StagedAuditedModel}}
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/StagedGroupedModel.html}{\texttt{StagedGroupedModel}}
\end{itemize}
You must implement these when you want to use certain features of the
Staging framework like automatic group mapping or entity level
\emph{Last Publish Date} handling. So how do you choose which is right
for you?
The \texttt{StagedAuditedModel} interface provides all the audit fields
to the model that implements it. You can check the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/AuditedModel.html}{\texttt{AuditedModel}}
interface for the specific audit fields provided. The
\texttt{StagedAuditedModel} interface is for models that function
independently from the group concept (sometimes referred to as company
models). If your model is a group model, you should not implement the
\texttt{StagedAuditedModel} interface.
The \texttt{StagedGroupedModel} interface must be implemented for group
models. For example, if your application requires the \texttt{groupId}
column, your model is a group model. If your model satisfies both the
\texttt{StagedGroupModel} and \texttt{StagedAuditedModel} requirements,
it should implement \texttt{StagedGroupedModel}. Your model should only
implement the \texttt{StagedAuditedModel} if it doesn't fulfill the
grouped model needs, but does fulfill the audited model needs. If your
model does not fulfill either the \texttt{StagedAuditedModel} or
\texttt{StagedGroupedModel} requirements, you should implement the base
\texttt{StagedModel} interface.
As an example for extending your model class, you can visit the
\href{https://docs.liferay.com/dxp/apps/collaboration/latest/javadocs/com/liferay/bookmarks/model/BookmarksEntryModel.html}{\texttt{BookmarksEntryModel}}
class, which extends the \texttt{StagedGroupedModel} interface; this is
done because bookmark entries are group models.
\begin{verbatim}
public interface BookmarksEntryModel extends BaseModel,
ShardedModel, StagedGroupedModel, TrashedModel, WorkflowedModel {
\end{verbatim}
Those are the differences between staged model interfaces.
\section{Staged Model Attributes}\label{staged-model-attributes}
One of the most important attributes used by the Staging framework is
the UUID (Universally Unique Identifier). This attribute must be set to
\texttt{true} in your \texttt{service.xml} file for Service Builder to
recognize your model as an eligible staged model. The UUID
differentiates entities between environments. Because the UUID always
remains the same, it's unique across multiple systems. Why is this so
important?
Suppose you're using
\href{/docs/7-2/user/-/knowledge_base/u/enabling-remote-live-staging}{remote
staging} and you create a new entity on your local staging site and
publish it to your remote live site. When you go back to modify the
entity on your local site and want to publish those changes, the UUID
shows that the local and remote entities are the same. The Staging
framework can thus recognize the original entity on the remote site and
update it. The UUID provides that.
In addition to the UUID, there are several columns that must be defined
in your \texttt{service.xml} file for Service Builder to define your
model as a staged model:
\begin{itemize}
\tightlist
\item
\texttt{companyId}
\item
\texttt{createDate}
\item
\texttt{modifiedDate}
\end{itemize}
If you want a staged grouped model, also include the \texttt{groupId}
and \texttt{lastPublishDate} columns. If you want a staged audited
model, include the \texttt{userId} and \texttt{userName} columns.
\section{Adapting Your Business Logic to Build Staged
Models}\label{adapting-your-business-logic-to-build-staged-models}
What if you don't want to extend your model with special attributes your
business logic doesn't need (removing the ability to leverage Service
Builder's auto-generation of staged models)? In this case, you should
adapt your business logic to meet the Staging framework's needs. Liferay
provides the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/adapter/builder/ModelAdapterBuilder.html}{\texttt{ModelAdapterBuilder}}
framework, which lets you adapt your model classes to staged models.
As an example, assume you have a completed app and you want it to work
with Staging. Your app, however, does not require a UUID for any of its
entities, and therefore, does not provide them. Instead of configuring
your app to handle UUIDs just for the sake of generating staged models,
you can leverage the Model Adapter Builder to build your staged models.
Another example for building staged models from scratch is for
applications that use REST services instead of the database to access
their attributes. Since this kind of app pulls its attributes from a
remote system, it is more convenient to build your staged models
yourself instead of relying on Service Builder, which is database
driven.
To adapt your model classes to staged models, follow the steps outlined
below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a \texttt{Staged{[}Entity{]}} interface that extends the
model-specific interface (e.g., \texttt{{[}Entity{]}}) and the
appropriate staged model interface (e.g., \texttt{StagedModel}). This
class serves as the Staged Model Adapter.
\item
Create a \texttt{Staged{[}Entity{]}Impl} class that implements the
\texttt{Staged{[}Entity{]}} interface and provides necessary logic for
your entity model to be recognized as a staged model.
\item
Create a \texttt{Staged{[}Entity{]}ModelAdapterBuilder} class that
implements
\texttt{ModelAdapterBuilder\textless{}{[}Entity{]},\ Staged{[}Entity{]}\textgreater{}}.
This class adapts the original model to the newly created Staged Model
Adapter.
\item
Adapt your existing model and call one of the provided APIs to export
or import the entity automatically.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/staged-model-adapter-diagram.png}
\caption{The Staged Model Adapter class extends your entity and staged
model interfaces.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/model-adapter-builder-diagram.png}
\caption{The Model Adapter Builder gets an instance of the model and
outputs a staged model.}
\end{figure}
To step through the process for leveraging the Model Adapter Builder for
an existing app, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-staged-models-manually}{Creating
Staged Models Manually}.
\chapter{Generating Staged Models Using Service
Builder}\label{generating-staged-models-using-service-builder}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Instead of having to create staged models for your app manually, you can
leverage
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} to generate the necessary staged model logic for you. If your
app doesn't use Liferay's Service Builder, you must configure it in your
project. This tutorial assumes you have a Service Builder project with
\texttt{*api} and \texttt{*service} modules. If you want to follow along
with this tutorial, download the staged-model-example Service Builder
project (Gradle-based).
You'll track the Service Builder-generated changes applied to an entity
model file to observe how staged models are assigned to your entity.
Keep in mind the specific
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-staged-models\#staged-model-attributes}{staged
attributes} necessary for each staged model. Depending on the attributes
defined in your \texttt{service.xml} file, Service Builder assigns your
entity model to a specific staged model type.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your project's \texttt{*service} module at the command
line. Run Service Builder (e.g., \texttt{gradlew\ buildService}) to
generate your project's models based on the current
\texttt{service.xml} configuration.
\item
Open your project's \texttt{{[}Entity{]}Model.java} interface and
observe the inherited interfaces.
\begin{verbatim}
public interface FooModel extends BaseModel, ShardedModel, StagedModel {
\end{verbatim}
Your model was generated as a staged model! This is because the
\texttt{service.xml} file sets the UUID to \texttt{true} and defines
the \texttt{companyId}, \texttt{createDate}, and \texttt{modifiedDate}
columns. There is much more logic generated for your app behind the
scenes, but this shows that Service Builder deemed your entity
eligible for the Staging and Export/Import frameworks.
\item
Add the \texttt{userId} and \texttt{userName} columns to your
\texttt{service.xml} file:
\begin{verbatim}
\end{verbatim}
\item
Rerun Service Builder and observe your \texttt{{[}Entity{]}Model.java}
interface again:
\begin{verbatim}
public interface FooModel extends BaseModel, GroupedModel, ShardedModel,
StagedAuditedModel {
\end{verbatim}
Your model is now a staged audited model!
\item
Add the \texttt{lastPublishDate} column to your \texttt{service.xml}
file:
\begin{verbatim}
\end{verbatim}
\item
Rerun Service Builder and observe your \texttt{{[}Entity{]}Model.java}
interface again:
\begin{verbatim}
public interface FooModel extends BaseModel, ShardedModel,
StagedGroupedModel {
\end{verbatim}
Your model is now a staged grouped model! The \texttt{groupId} column
is also required to extend the \texttt{StagedGroupedModel} interface,
but it was already defined in the original \texttt{service.xml} file.
\end{enumerate}
Fantastic! You've witnessed firsthand how easy it is to generate staged
models using Service Builder.
\chapter{Creating Staged Models
Manually}\label{creating-staged-models-manually}
There are times when using
\href{/docs/7-2/frameworks/-/knowledge_base/f/generating-staged-models-using-service-builder}{Service
Builder to generate your staged models} is not practical. In these
cases, you should create your staged models manually. Make sure to read
the
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-staged-models\#adapting-your-business-logic-to-build-staged-models}{Adapting
Your Business Logic to Build Staged Models} section to determine if
creating staged models manually is beneficial for your use case.
In this tutorial, you'll explore how the Asset Link framework (a Liferay
DXP framework used for
\href{/docs/7-2/user/-/knowledge_base/u/defining-content-relationships}{relating
assets}) manually creates staged models. This framework is separate from
Export/Import and is referenced solely as an example for how to leverage
the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/adapter/builder/ModelAdapterBuilder.html}{ModelAdapterBuilder}
framework, which lets you adapt your model classes to staged models.
Follow the steps below to leverage the Model Adapter Builder in your
app.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a new interface that extends one of the
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-staged-models\#staged-model-interfaces}{staged
model interfaces} and your model specific interface. For example,
\begin{verbatim}
public interface StagedAssetLink extends AssetLink, StagedModel {
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Staged model interfaces typically follow the `Staged[Entity]`
naming convention. The Asset Link framework uses a generic entity called
`AssetLink`.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Define methods required for your model to qualify as a staged model.
For asset links, methods for retrieving entry UUIDs (among others) are
defined:
\begin{verbatim}
public String getEntry1Uuid();
public String getEntry2Uuid();
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Asset links do not provide UUIDs by default; however, they still
need to be tracked in the Staging and Export/Import frameworks. Therefore,
they require staged models. Since they don't provide a UUID, Service
Builder cannot generate staged models for asset links. The Asset Link
framework has to create staged models differently using the Model Adapter
Builder.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
These will be implemented by a new implementation class later.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Create an implementation class that implements your new
\texttt{Staged{[}Entity{]}}. For example, the Asset Link framework
does this:
\begin{verbatim}
public class StagedAssetLinkImpl implements StagedAssetLink {
}
\end{verbatim}
This class provides necessary logic for your entity model to be
recognized as a staged model. Below is a subset of logic in the
example \texttt{StagedAssetLinkImpl} class used to populate UUIDs for
asset link entries:
\begin{verbatim}
public StagedAssetLinkImpl(AssetLink assetLink) {
_assetLink = assetLink;
...
populateUuid();
}
@Override
public String getEntry1Uuid() {
if (Validator.isNotNull(_entry1Uuid)) {
return _entry1Uuid;
}
populateEntry1Attributes();
return _entry1Uuid;
}
@Override
public String getEntry2Uuid() {
if (Validator.isNotNull(_entry2Uuid)) {
return _entry2Uuid;
}
populateEntry2Attributes();
return _entry2Uuid;
}
protected void populateEntry1Attributes() {
...
AssetEntry entry1 = AssetEntryLocalServiceUtil.fetchAssetEntry(
_assetLink.getEntryId1());
...
_entry1Uuid = entry1.getClassUuid();
}
protected void populateEntry2Attributes() {
...
AssetEntry entry2 = AssetEntryLocalServiceUtil.fetchAssetEntry(
_assetLink.getEntryId2());
...
_entry2Uuid = entry2.getClassUuid();
}
protected void populateUuid() {
...
String entry1Uuid = getEntry1Uuid();
String entry2Uuid = getEntry2Uuid();
...
_uuid = entry1Uuid + StringPool.POUND + entry2Uuid;
}
}
private AssetLink _assetLink;
private String _entry1Uuid;
private String _entry2Uuid;
private String _uuid;
\end{verbatim}
This logic retrieves asset link entries and populates UUIDs for them
usable by the Staging and Export/Import frameworks. With the newly
generated UUIDs, asset link model classes can be converted to staged
models.
\item
Create a Model Adapter Builder class and implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/adapter/builder/ModelAdapterBuilder.html}{ModelAdapterBuilder}
interface. You should define the entity type and your Staged Model
Adapter class when implementing the interface:
\begin{verbatim}
public class StagedAssetLinkModelAdapterBuilder
implements ModelAdapterBuilder {
@Override
public StagedAssetLink build(AssetLink assetLink) {
return new StagedAssetLinkImpl(assetLink);
}
}
\end{verbatim}
For the \texttt{StagedAssetLinkModelAdapterBuilder}, the entity type
is \texttt{AssetLink} and the Staged Model Adapter is
\texttt{StagedAssetLink}. Your app should follow a similar design. The
Model Adapter Builder outputs a new instance of the
\texttt{Staged{[}Entity{]}Impl} object.
\item
Now you need to adapt your existing business logic to call the
provided APIs. You can call the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/adapter/ModelAdapterUtil.html}{ModelAdapterUtil}
class to create an instance of your Staged Model Adapter:
\begin{verbatim}
StagedAssetLink stagedAssetLink = ModelAdapterUtil.adapt(
assetLink, AssetLink.class, StagedAssetLink.class);
\end{verbatim}
Once you've created
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-staged-model-data-handlers}{Staged
Model Data Handlers}, you can begin exporting/importing your now
Staging-compatible entities:
\begin{verbatim}
StagedModelDataHandlerUtil.exportStagedModel(
portletDataContext, stagedAssetLink);
\end{verbatim}
\end{enumerate}
Awesome! You've successfully adapted your business logic to build staged
models!
\chapter{Developing Data Handlers}\label{developing-data-handlers}
A common requirement for data driven applications is to import and
export data. This \emph{could} be accomplished by accessing your
database directly and running SQL queries to export/import data;
however, this has several drawbacks:
\begin{itemize}
\tightlist
\item
Working with different database vendors might require customized SQL
scripts.
\item
Access to the database may be tightly controlled, restricting the
ability to export/import on demand.
\item
You'd have to come up with your own means of storing and parsing the
data.
\end{itemize}
Liferay provides data handlers as a convenient and reliable way to
export/import your data (as a LAR file).
There are two types of data handlers:
\begin{itemize}
\tightlist
\item
Portlet Data Handlers
\item
Staged Model Data Handlers
\end{itemize}
A Portlet Data Handler imports/exports portlet specific data to a LAR
file. These classes only have the role of querying and coordinating
between staged model data handlers. For example, the Bookmarks
application's portlet data handler tracks system events dealing with
Bookmarks entities. It also configures the Export/Import UI options for
the Bookmarks application.
A Staged Model Data Handler supplies information about a staged model
(entity) to the Export/Import framework, defining a display name for the
UI, deleting an entity, and exporting referenced content. For example,
if a Bookmarks entry resides in a Bookmarks folder, the
\texttt{BookmarksEntry} staged model data handler invokes the export of
the \texttt{BookmarksFolder}.
\begin{figure}
\centering
\includegraphics{./images/data-handler-diagram.png}
\caption{The Data Handler framework uses portlet data handlers and
staged model data handlers to track and export/import portlet and staged
model information, respectively.}
\end{figure}
You're not required to implement a staged model data handler for every
entity in your application, but they're necessary for any entity you
want to export/import or have the staging framework track.
\noindent\hrulefill
\textbf{Note:} Creating data handlers for your app means it's
automatically tracked by the Staging framework. You can further
customize how Staging handles your app, but creating staged models and
data handlers is what registers your app for Staging.
\noindent\hrulefill
Before implementing data handlers, make sure your application is ready
for the Export/Import and Staging frameworks by creating
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-staged-models}{staged
models}.
\section{\texorpdfstring{Understanding the \texttt{PortletDataHandler}
Interface}{Understanding the PortletDataHandler Interface}}\label{understanding-the-portletdatahandler-interface}
A Portlet Data Handler imports/exports portlet specific data to a LAR
file. These classes query and coordinate between
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-staged-model-data-handlers}{staged
model data handlers}.
To create a portlet data handler for your staged model, you must
implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/PortletDataHandler.html}{\texttt{PortletDataHandler}}
interface by extending the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/BasePortletDataHandler.html}{\texttt{BasePortletDataHandler}}
class. Visit the API reference documentation for this interface/class
for useful information on the methods provided.
Some guidelines for implementing the \texttt{PortletDataHandler}
interface are provided below:
The \texttt{@Component} annotation section above the implementation
class's declaration registers the class as a portlet data handler in the
OSGi service registry. There are a few annotation attributes you should
set:
\texttt{immediate}: activates the component immediately once its
provided module has started.
\texttt{property}: sets various properties for the component service.
You must associate the portlet you wish to handle with this service so
they function properly in the export/import environment. You should have
one portlet data handler for each portlet (e.g., Bookmarks and Bookmarks
Admin).
\texttt{service}: points to the \texttt{PortletDataHandler.class}
interface.
The \texttt{activate} method sets what the portlet data handler
controls. It also configures the portlet's Export/Import and Staging UI.
This method is called during initialization of the component by the
\href{https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Activate.html}{\texttt{@Activate}}
annotation; it's invoked after dependencies are set and before services
are registered. Five callable \texttt{set} methods are described below:
\texttt{setDataPortletPreferences}: sets portlet preferences your app
should handle.
\texttt{setDeletionSystemEventStagedModelTypes}: sets the staged model
deletions that the portlet data handler should track. For example, the
Bookmarks app tracks Bookmark entries and folders.
\texttt{setPublishToLiveByDefault}: controls whether your app is
selected to publish on the Publication screen by default.
\texttt{setExportControls}: adds fine grained controls over
export/import behavior rendered in the Export/Import UI. This also sets
the \texttt{setImportControls} method. For example, the Bookmarks app
adds a checkbox to select Bookmarks content (entries) to export.
\texttt{setStagingControls}: adds fine-grained controls over staging
behavior rendered in the Staging UI. For example, this enables your
app's checkboxes in the Content section displayed during publication.
The \texttt{doExportData} method checks if anything should be exported.
For example, the Bookmarks app uses this method to check if the user
selected Bookmarks entries for export by leveraging the
\texttt{portletDataContext}. Later, the
\texttt{ExportImportActionableDynamicQuery} framework runs a query
against bookmarks folders and entries to find ones which should be
exported to the LAR file.
The \texttt{-ActionableDynamicQuery} classes are generated automatically
by Service Builder and are available in an app's local services. It
queries the database searching for certain Export/Import-specific
parameters (e.g., \texttt{createDate} and \texttt{modifiedDate}), and
based on those parameters, finds a list of exportable records from the
staged model data handler.
The \texttt{doImportData} method queries for entity data in the imported
LAR file that should be added to the database. This is done by
extracting XML elements from the LAR file by using utility methods in
the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/StagedModelDataHandlerUtil.html}{\texttt{StagedModelDataHandlerUtil}}
class. The extracted elements tell Liferay DXP what data to import from
the LAR file.
The \texttt{doPrepareManifestSummary} method calculates the number of
affected entities based on the current export or staging process.
You must retrieve and manage the schema version. This is done with the
\texttt{getSchemaVersion} and \texttt{validateSchemaVersion} methods.
The schema version is used to perform component related validation
before importing data. It's added to the
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-archive-lar-file}{LAR
file} for each application being processed. During import, the
environment's schema version is compared to the LAR file's schema
version. Validating the schema version avoids broken data when
importing. See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/PortletDataHandler.html\#getSchemaVersion--}{\texttt{PortletDataHandler.getSchemaVersion()}}
method's Javadoc for more information.
Next you'll learn about the \texttt{StagedModelDataHandler} interface.
\section{\texorpdfstring{Understanding the
\texttt{StagedModelDataHandler}
Interface}{Understanding the StagedModelDataHandler Interface}}\label{understanding-the-stagedmodeldatahandler-interface}
A Staged Model Data Handler supplies information about a staged model
(entity) to the Export/Import framework, defines a display name for the
UI, deletes entities, etc. It's also responsible for exporting
referenced content. For example, if a Bookmarks entry resides in a
Bookmarks folder, the \texttt{BookmarksEntry} staged model data handler
invokes the export of the \texttt{BookmarksFolder}.
To create a staged model data handler for your staged model, you must
implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/StagedModelDataHandler.html}{\texttt{StagedModelDataHandler}}
interface. This is typically done by extending the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/BaseStagedModelDataHandler.html}{\texttt{BaseStagedModelDataHandler}}
class. Visit the API reference documentation for this interface/class
for useful information on the methods provided.
Additional implementation details for the
\texttt{StagedModelDataHandler} interface is provided below:
The \texttt{@Component} annotation section above the implementation
class's declaration registers the class as a staged model data handler
in the OSGi service registry. There are two annotation attributes you
should set:
\texttt{immediate}: activates the component immediately once its
provided module has started.
\texttt{service}: points to the \texttt{StagedModelDataHandler.class}
interface.
The \texttt{getClassNames} method provides the class names of the models
the data handler tracks. As a best practice, you should have one staged
model data handler per staged model. It's possible to use multiple class
types, but this is not recommended.
The \texttt{getDisplayName} method retrieves the staged model's display
name. This is used in the Export/Import UI.
The \texttt{doExportStagedModel} method retrieves your app entity's data
element from the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/PortletDataContext.html}{\texttt{PortletDataContext}}
and then adds the class model characterized by that data element to the
\texttt{PortletDataContext}. The \texttt{PortletDataContext} data
populates the LAR file with your application's data during the export
process.
\noindent\hrulefill
\textbf{Note:} A staged model data handler should ensure everything
required for its operation is also exported. For example, in the
Bookmarks application, an entry requires its folder to keep the folder
structure intact. Therefore, the folder should be exported first
followed by the entry. Note that once an entity has been exported,
subsequent calls to the export method don't repeat the export process
multiple times, ensuring optimal performance.
\noindent\hrulefill
The \texttt{doImportStagedModel} method imports the staged model data.
An important feature of the import process is that all exported
reference elements are automatically imported when needed. The method
must therefore only find the new assigned ID for the folder before
importing the entry.
The \texttt{PortletDataContext} keeps the data up-to-date during the
import process. The old ID and new ID mapping can be reached by using
the \texttt{portletDataContext.getNewPrimaryKeysMap()} method. This
method also checks the import mode (e.g., \emph{Copy As New} or
\emph{Mirror}) and, depending on the process configuration and existing
environment, adds or updates the entry.
The \texttt{doImportMissingReference} method maps the existing staged
model to the old ID in the reference element. When a reference is
exported as missing, the Data Handler framework calls this method during
the import process and updates the new primary key map in the portlet
data context.
When importing a LAR (i.e., publishing to the live Site), the import
process expects all of an entity's references to be available and
validates their existence.
For example, if you republish an updated bookmarks folder to the live
Site and did not include some of its existing entries in the
publication, these entries are considered missing references.
Since you have references from two separate Sites with differing IDs,
the system can't match them during publication. Suppose you export a
bookmark entry as a missing reference with a primary key (ID) of
\texttt{1}. When importing that information, the LAR only provides the
ID but not the entry itself. Therefore, during the import process, the
Data Handler framework searches for the entry to replace by its UUID,
but the entry to replace has a different ID (primary key) of \texttt{2}.
You must provide a way to handle these missing references.
To do this, you must add a method that maps the missing reference's
primary key from the export to the existing primary key during import.
Since the reference's UUID is consistent across systems, it's used to
complete the mapping of differing primary keys. Note that a reference
can only be missing on the live Site if it has already been published
previously. Therefore, when publishing a bookmarks folder for the first
time, the system doesn't check for missing references.
Continue in the section to learn how to develop data handlers for your
app.
\chapter{Creating Portlet Data
Handlers}\label{creating-portlet-data-handlers}
In this tutorial, you'll create the
\texttt{BookmarksPortletModelDataHandler} class used for the Bookmarks
application. The Bookmarks application's portlet data handler tracks
system events dealing with Bookmarks entities. It also configures the
Export/Import and Staging UI options for the Bookmarks application.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a new package in your existing Service Builder project for your
data handler classes. For instance, the Bookmarks application's data
handler classes reside in the \texttt{bookmarks-service} module's
\texttt{com.liferay.bookmarks.internal.exportimport.data.handler}
package.
\item
Create your \texttt{-PortletDataHandler} class for your application in
the new \texttt{-exportimport.data.handler} package and have it
implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/PortletDataHandler.html}{PortletDataHandler}
interface by extending the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/BasePortletDataHandler.html}{BasePortletDataHandler}
class. For example,
\begin{verbatim}
public class BookmarksPortletDataHandler extends BasePortletDataHandler {
\end{verbatim}
\item
Create an \texttt{@Component} annotation section above the class
declaration:
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + BookmarksPortletKeys.BOOKMARKS
},
service = PortletDataHandler.class
)
\end{verbatim}
\item
Set what the portlet data handler controls and the portlet's
Export/Import and Staging UIs by adding an \texttt{activate} method:
\begin{verbatim}
@Activate
protected void activate() {
setDataPortletPreferences("rootFolderId");
setDeletionSystemEventStagedModelTypes(
new StagedModelType(BookmarksEntry.class),
new StagedModelType(BookmarksFolder.class));
setExportControls(
new PortletDataHandlerBoolean(
NAMESPACE, "entries", true, false, null,
BookmarksEntry.class.getName()));
setStagingControls(getExportControls());
}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/export-import-controls.png}
\caption{You can select the content types you'd like to export/import
in the UI.}
\end{figure}
\item
For the Bookmarks portlet data handler to reference its entry and
folder staged models successfully, you must set them in your class:
\begin{verbatim}
@Reference(unbind = "-")
protected void setBookmarksEntryLocalService(
BookmarksEntryLocalService bookmarksEntryLocalService) {
_bookmarksEntryLocalService = bookmarksEntryLocalService;
}
@Reference(unbind = "-")
protected void setBookmarksFolderLocalService(
BookmarksFolderLocalService bookmarksFolderLocalService) {
_bookmarksFolderLocalService = bookmarksFolderLocalService;
}
private BookmarksEntryLocalService _bookmarksEntryLocalService;
private BookmarksFolderLocalService _bookmarksFolderLocalService;
\end{verbatim}
The \texttt{set} methods must be annotated with the
\href{https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Reference.html}{@Reference}
annotation.
\textbf{Important:} Liferay DXP's official Bookmarks app does not use
local services in its portlet data handler; instead, it uses the
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/exportimport/staged/model/repository/StagedModelRepository.html}{\texttt{StagedModelRepository}}
framework. This is a new framework, but is a viable option when
setting up your portlet data handlers. For more information on this,
see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/providing-entity-specific-local-services-for-export-import}{Providing
Entity-Specific Local Services for Staging} tutorial section. Since
local services are more widely used in custom apps, this tutorial
covers those instead.
\item
You must create a namespace for your entities so the Export/Import
framework can identify your application's entities from other entities
in Liferay DXP. The Bookmarks application's namespace declaration
looks like this:
\begin{verbatim}
public static final String NAMESPACE = "bookmarks";
\end{verbatim}
You'll see how this namespace is used later.
\item
Your portlet data handler should retrieve the data related to its
staged model entities so it can properly export/import it. Add this
functionality by inserting the following methods:
\begin{verbatim}
@Override
protected String doExportData(
final PortletDataContext portletDataContext, String portletId,
PortletPreferences portletPreferences)
throws Exception {
Element rootElement = addExportDataRootElement(portletDataContext);
if (!portletDataContext.getBooleanParameter(NAMESPACE, "entries")) {
return getExportDataRootElementString(rootElement);
}
portletDataContext.addPortletPermissions(
BookmarksConstants.RESOURCE_NAME);
rootElement.addAttribute(
"group-id", String.valueOf(portletDataContext.getScopeGroupId()));
ExportActionableDynamicQuery folderActionableDynamicQuery =
_bookmarksFolderLocalService.
getExportActionableDynamicQuery(portletDataContext);
folderActionableDynamicQuery.performActions();
ActionableDynamicQuery entryActionableDynamicQuery =
_bookmarksEntryLocalService.
getExportActionableDynamicQuery(portletDataContext);
entryActionableDynamicQuery.performActions();
return getExportDataRootElementString(rootElement);
}
@Override
protected PortletPreferences doImportData(
PortletDataContext portletDataContext, String portletId,
PortletPreferences portletPreferences, String data)
throws Exception {
if (!portletDataContext.getBooleanParameter(NAMESPACE, "entries")) {
return null;
}
portletDataContext.importPortletPermissions(
BookmarksConstants.RESOURCE_NAME);
Element foldersElement = portletDataContext.getImportDataGroupElement(
BookmarksFolder.class);
List folderElements = foldersElement.elements();
for (Element folderElement : folderElements) {
StagedModelDataHandlerUtil.importStagedModel(
portletDataContext, folderElement);
}
Element entriesElement = portletDataContext.getImportDataGroupElement(
BookmarksEntry.class);
List entryElements = entriesElement.elements();
for (Element entryElement : entryElements) {
StagedModelDataHandlerUtil.importStagedModel(
portletDataContext, entryElement);
}
return null;
}
\end{verbatim}
\item
Add a method that counts the number of affected entities based on the
current export or staging process:
\begin{verbatim}
@Override
protected void doPrepareManifestSummary(
PortletDataContext portletDataContext,
PortletPreferences portletPreferences)
throws Exception {
if (ExportImportDateUtil.isRangeFromLastPublishDate(
portletDataContext)) {
_staging.populateLastPublishDateCounts(
portletDataContext,
new StagedModelType[] {
new StagedModelType(BookmarksEntry.class.getName()),
new StagedModelType(BookmarksFolder.class.getName())
});
return;
}
ActionableDynamicQuery entryExportActionableDynamicQuery =
_bookmarksEntryLocalService.
getExportActionableDynamicQuery(portletDataContext);
entryExportActionableDynamicQuery.performCount();
ActionableDynamicQuery folderExportActionableDynamicQuery =
_bookmarksFolderLocalService.
getExportActionableDynamicQuery(portletDataContext);
folderExportActionableDynamicQuery.performCount();
}
\end{verbatim}
This number is displayed in the Export and Staging UI. Note that since
the Staging framework traverses the entity graph during export, the
built-in components provide an approximate value in some cases.
\begin{figure}
\centering
\includegraphics{./images/manifest-summary-count.png}
\caption{The number of modified Bookmarks entities are displayed in
the Export UI.}
\end{figure}
\item
Set the XML schema version for the XML files included in your exported
LAR file:
\begin{verbatim}
public static final String SCHEMA_VERSION = "1.0.0";
@Override
public String getSchemaVersion() {
return SCHEMA_VERSION;
}
@Override
public boolean validateSchemaVersion(String schemaVersion) {
return _portletDataHandlerHelper.validateSchemaVersion(
schemaVersion, getSchemaVersion());
}
\end{verbatim}
\end{enumerate}
Awesome! You've set up your portlet data handler and your application
can now support the Export/Import framework and display a UI for it. Be
sure to also implement staged model data handlers for your staged
models. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-staged-model-data-handlers}{Creating
Staged Model Data Handlers} for more information.
\chapter{Creating Staged Model Data
Handlers}\label{creating-staged-model-data-handlers}
In this tutorial, you'll create the
\texttt{BookmarksStagedModelDataHandler} class used for the Bookmarks
application. The Bookmarks application has two staged models: entries
and folders. Creating data handlers for these two entities is similar,
so you'll examine how this is done for Bookmark entries. This tutorial
assumes you've already created
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-staged-models}{staged
models}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a new package in your existing Service Builder project for your
data handler classes. For instance, the Bookmarks application's data
handler classes reside in the \texttt{bookmarks-service} module's
\texttt{com.liferay.bookmarks.internal.exportimport.data.handler}
package.
\item
Create a \texttt{-StagedModelDataHandler} class in the
\texttt{-exportimport.data.handler} package. The staged model data
handler class should extend the
\href{https://docs.liferay.com/dxp/portal/7.1-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/BaseStagedModelDataHandler.html}{\texttt{BaseStagedModelDataHandler}}
class and the entity type should be specified as its parameter. You
can see how this was done for the
\texttt{BookmarksEntryStagedModelDataHandler} class below:
\begin{verbatim}
public class BookmarksEntryStagedModelDataHandler
extends BaseStagedModelDataHandler {
\end{verbatim}
\item
Create an \texttt{@Component} annotation section above the class
declaration.
\begin{verbatim}
@Component(immediate = true, service = StagedModelDataHandler.class)
\end{verbatim}
\item
Create a getter and setter method for the local service of the staged
model for which you want to provide a data handler:
\begin{verbatim}
@Override
protected BookmarksEntryLocalService getBookmarksEntryLocalService() {
return _bookmarksEntryLocalService;
}
@Reference(unbind = "-")
protected void setBookmarksEntryLocalService(
BookmarksEntryLocalService bookmarksEntryLocalService) {
_bookmarksEntryLocalService = bookmarksEntryLocalService;
}
private BookmarksEntryLocalService _bookmarksEntryLocalService;
\end{verbatim}
These methods are used to link this data handler with the staged model
for bookmark entries.
\textbf{Important:} Liferay DXP's official Bookmarks app does not use
local services in its staged model data handlers; instead, it uses the
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/exportimport/staged/model/repository/StagedModelRepository.html}{\texttt{StagedModelRepository}}
framework. This is a new framework, but is a viable option when
setting up your staged model data handlers. For more information on
this, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/providing-entity-specific-local-services-for-export-import}{Providing
Entity-Specific Local Services for Staging} tutorial section. Since
local services are more widely used in custom apps, this tutorial
covers those instead.
\item
Provide the class names of the models the data handler tracks. You can
do this by overriding the
\href{https://docs.liferay.com/dxp/portal/7.1-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/StagedModelDataHandler.html}{\texttt{StagedModelDataHandler}}'s
\texttt{getClassnames()} method:
\begin{verbatim}
public static final String[] CLASS_NAMES = {BookmarksEntry.class.getName()};
@Override
public String[] getClassNames() {
return CLASS_NAMES;
}
\end{verbatim}
\item
Add a method that retrieves the staged model's display name:
\begin{verbatim}
@Override
public String getDisplayName(BookmarksEntry entry) {
return entry.getName();
}
\end{verbatim}
The display name is presented with the progress bar during the
export/import process.
\begin{figure}
\centering
\includegraphics{./images/staged-model-display-name.png}
\caption{Your staged model data handler provides the display name in
the Export/Import UI.}
\end{figure}
\item
Add methods that import and export your staged model and its
references.
\begin{verbatim}
@Override
protected void doExportStagedModel(
PortletDataContext portletDataContext, BookmarksEntry entry)
throws Exception {
if (entry.getFolderId() !=
BookmarksFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
StagedModelDataHandlerUtil.exportReferenceStagedModel(
portletDataContext, entry, entry.getFolder(),
PortletDataContext.REFERENCE_TYPE_PARENT);
}
Element entryElement = portletDataContext.getExportDataElement(entry);
portletDataContext.addClassedModel(
entryElement, ExportImportPathUtil.getModelPath(entry), entry);
}
@Override
protected void doImportStagedModel(
PortletDataContext portletDataContext, BookmarksEntry entry)
throws Exception {
Map folderIds =
(Map)portletDataContext.getNewPrimaryKeysMap(
BookmarksFolder.class);
long folderId = MapUtil.getLong(
folderIds, entry.getFolderId(), entry.getFolderId());
ServiceContext serviceContext =
portletDataContext.createServiceContext(entry);
BookmarksEntry importedEntry = null;
if (portletDataContext.isDataStrategyMirror()) {
BookmarksEntry existingEntry =
_bookmarksEntryLocalService. fetchBookmarksEntryByUuidAndGroupId(
entry.getUuid(), portletDataContext.getScopeGroupId());
if (existingEntry == null) {
serviceContext.setUuid(entry.getUuid());
importedEntry = _bookmarksEntryLocalService.addEntry(
userId, portletDataContext.getScopeGroupId(), folderId, entry.getName(), entry.getUrl(), entry.getDescription(), serviceContext);
}
else {
importedEntry = _bookmarksEntryLocalService.updateEntry(
userId, existingEntry.getEntryId(), portletDataContext.getScopeGroupId(), folderId, entry.getName(), entry.getUrl(), entry.getDescription(), serviceContext);
}
}
else {
importedEntry = _bookmarksEntryLocalService.addEntry(userId, portletDataContext.getScopeGroupId(), folderId,entry.getName(), entry.getUrl(), entry.getDescription(), serviceContext);
}
portletDataContext.importClassedModel(entry, importedEntry);
}
\end{verbatim}
\item
Add the \texttt{doImportMissingReference} method to your class:
\begin{verbatim}
@Override
protected void doImportMissingReference(
PortletDataContext portletDataContext, String uuid, long groupId,
long entryId)
throws Exception {
BookmarksEntry existingEntry = fetchMissingReference(uuid, groupId);
if (existingEntry == null) {
return;
}
Map entryIds =
(Map)portletDataContext.getNewPrimaryKeysMap(
BookmarksEntry.class);
entryIds.put(entryId, existingEntry.getEntryId());
}
\end{verbatim}
\end{enumerate}
Fantastic! You've created a data handler for your staged model. The
Export/Import framework can now track your entity's behavior and data.
Be sure to also implement a portlet data handler to manage portlet
specific data. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-portlet-data-handlers}{Creating
Portlet Data Handlers} article to do this for the Bookmarks app.
\chapter{Providing Entity-Specific Local Services for
Export/Import}\label{providing-entity-specific-local-services-for-exportimport}
Data handlers must often call your app's local services to perform
Export/Import-related tasks for its entities. When the Export/Import
framework operates on entities (i.e., staged models), it often cannot
manage important information from the entity's local services alone. The
\emph{Staged Model Repository} framework removes this barrier by linking
an app's staged model to a local service. This gives you access to
entity-specific methods tailored specifically for the staged model data
you're handling.
What kind of \emph{entity-specific} methods are we talking about here?
Your data handlers only expose a specific set of actions, like export
and import methods. The Staged Model Repository framework provides CRUD
operations for a specific staged model that local services don't expose.
The staged model repository does not avoid using your app's local
services. It only provides an additional layer that provides
Export/Import-specific functionality. Here's how this works:
\begin{itemize}
\tightlist
\item
\texttt{*StagedModelDataHandler}: de-serializes the provided
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-archive-lar-file}{LAR
file's} XML into a model.
\item
\texttt{*StagedModelRepository}: updates the model based on the
environment and business logic, providing entity-specific CRUD
operations for Staging purposes (e.g., UUID manipulation).
\item
Local services are called from the \texttt{*StagedModelRepository} and
handle the remainder of the process.
\end{itemize}
Pretty cool, right? The first thing you must do is implement the
\texttt{StagedModelRepository} interface. You'll explore this next.
\section{\texorpdfstring{Understanding the
\texttt{StagedModelRepository}
Interface}{Understanding the StagedModelRepository Interface}}\label{understanding-the-stagedmodelrepository-interface}
Providing specialized local services for your app's staging
functionality lets you abstract the additional staging-specific
information away from your data handlers. Before you can begin using the
Staged Model Repository framework in your app, you must implement it.
The first important task is adding an \texttt{@Component} annotation
section above the implementation class's declaration. This registers the
class as a staged model repository in the OSGi service registry. There
are a few annotation attributes you should set:
\begin{itemize}
\tightlist
\item
\texttt{immediate}: activates the component immediately once its
provided module has started.
\item
\texttt{property}: sets various properties for the component service.
You must associate the model class you wish to handle with this
service so it's recognized by the data handlers leveraging it. This
property must set the fully qualified model class name like this:
\texttt{property\ =\ \ \ "model.class.name=FULLY\_QUALIFIED\_MODEL\_CLASS"}.
\item
\texttt{service}: points to the \texttt{StagedModelRepository.class}
interface.
\end{itemize}
Next, you must implement the \texttt{StagedModelRepository} interface.
Implementations vary greatly based on the app you're implementing it
for. There are two common cases you'll experience when implementing its
methods:
\begin{itemize}
\tightlist
\item
Methods that require additional Export/Import information injected
before calling the local service.
\item
Methods that can call the local service directly.
\end{itemize}
The
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-a1/modules/apps/bookmarks/bookmarks-service/src/main/java/com/liferay/bookmarks/internal/exportimport/staged/model/repository/BookmarksEntryStagedModelRepository.java\#L51-L71}{\texttt{BookmarksEntryStagedModelRepository.addStagedModel(...)}}
method is an example where only calling the local service would not
satisfy the staged model data handler's needs (i.e., its UUID
requirement). With the staged model repository layer, however, you can
add export/import specific requirements on top of the present local
services to serve your data handlers' needs.
The method does this:
\begin{itemize}
\tightlist
\item
Sets the user ID and service context based on the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/PortletDataContext.html}{\texttt{PortletDataContext}}
(used to populate the LAR file with your application's data during the
export process).
\item
Sets the UUID, which is required to differentiate staged content
between Sites.
\item
Calls the entity's local service.
\end{itemize}
Not every method implementation requires additional export/import
information. For example, deleting Bookmarks Entries and deleting
Bookmarks Entry staged models are functionally the same, so your staged
model repository's method would call the local service directly (e.g.,
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-a1/modules/apps/bookmarks/bookmarks-service/src/main/java/com/liferay/bookmarks/internal/exportimport/staged/model/repository/BookmarksEntryStagedModelRepository.java\#L73-L78}{\texttt{BookmarksEntryStagedModelRepository.deleteStagedModel(...)}}).
Next you'll learn about using a Staged Model Repository.
\section{Using a Staged Model
Repository}\label{using-a-staged-model-repository}
You can leverage a staged model repository by
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Creating a getter and setter method to make a
\texttt{StagedModelRepository} object available to your entity.
\item
Calling the \texttt{StagedModelRepository} object to leverage its
specialized export/import logic.
\end{enumerate}
The getter and setter methods instantiate a
\texttt{StagedModelRepository} object that the staged model data handler
can use to access your entity's CRUD operations. The setter method
should have an \texttt{@Reference} annotation listed above its method
signature. This injects the component service of the
\texttt{*StagedModelRepository} into the staged model repository object.
The component service was created when you set the \texttt{@Component}
annotation in the implementation class.
Once you have access to the \texttt{StagedModelRepository} object, call
it to use its specialized export/import logic. Now that you have access
to CRUD operations via the \texttt{StagedModelRepository} object, you
can skip the headache of providing a slew of parameters and additional
functionality in the local service to do simple things like add a
Bookmarks entry. The staged model repository abstracts these
requirements away from the data handler.
Continue in the section to learn how to develop staged model
repositories for your app.
\chapter{Implementing the Staged Model Repository
Framework}\label{implementing-the-staged-model-repository-framework}
In this article, you'll step through a quick example that demonstrates
implementing the \texttt{StagedModelRepository} interface to use for a
staged model. This example references Liferay's Bookmarks app and
Bookmarks Entry entities.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your app's \texttt{-service} bundle, create a package that holds
your Staged Model Repository classes (e.g.,
\texttt{com.liferay.bookmarks.exportimport.staged.model.repository}).
If you do not have a \texttt{-service} bundle, visit the
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} articles for info on generating an app's services. You must
have them to leverage most Export/Import and Staging features.
\item
Create your \texttt{-StagedModelRepository} class in the new package
and implement the \texttt{StagedModelRepository} interface in the
class' declaration. For example,
\begin{verbatim}
public class BookmarksEntryStagedModelRepository
implements StagedModelRepository {
\end{verbatim}
Be sure also to include the staged model type parameter for this
repository (e.g., \texttt{BookmarksEntry}).
\item
Add an \texttt{@Component} annotation for your staged model repository
class that looks like this:
\begin{verbatim}
@Component(
immediate = true,
property = "model.class.name=com.liferay.bookmarks.model.BookmarksEntry",
service = StagedModelRepository.class
)
\end{verbatim}
\item
Implement the \texttt{StagedModelRepository} interface's methods in
your staged model repository. You can reference the
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/exportimport/staged/model/repository/StagedModelRepository.html}{Javadoc}
for this interface to learn what each method is intended for.
As an example, you'll set a couple method implementations to get a
taste for how it works.
\item
Implement the \texttt{addStagedModel(...)} method. The Bookmarks entry
example looks like this:
\begin{verbatim}
@Override
public BookmarksEntry addStagedModel(
PortletDataContext portletDataContext,
BookmarksEntry bookmarksEntry)
throws PortalException {
long userId = portletDataContext.getUserId(
bookmarksEntry.getUserUuid());
ServiceContext serviceContext = portletDataContext.createServiceContext(
bookmarksEntry);
if (portletDataContext.isDataStrategyMirror()) {
serviceContext.setUuid(bookmarksEntry.getUuid());
}
return _bookmarksEntryLocalService.addEntry(
userId, bookmarksEntry.getGroupId(), bookmarksEntry.getFolderId(),
bookmarksEntry.getName(), bookmarksEntry.getUrl(),
bookmarksEntry.getDescription(), serviceContext);
}
\end{verbatim}
This provides the UUID for the local service.
\item
Not every method implementation requires additional staging
information. Implementing the \texttt{deleteStagedModels} method calls
the local service directly.
\begin{verbatim}
@Override
public void deleteStagedModels(PortletDataContext portletDataContext)
throws PortalException {
_bookmarksEntryLocalService.deleteEntries(
portletDataContext.getScopeGroupId(),
BookmarksFolderConstants.DEFAULT_PARENT_FOLDER_ID);
}
\end{verbatim}
\item
Finish implementing the \texttt{StagedModelRepository} so it's usable
in your data handlers.
\end{enumerate}
Awesome! You've implemented the Staged Model Repository framework for
your app! If you're interested in leveraging this framework after the
implementation process, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-staged-model-repository-framework}{Using
the Staged Model Repository Framework} article.
\chapter{Using the Staged Model Repository
Framework}\label{using-the-staged-model-repository-framework}
Leveraging the Staged Model Repository framework in your app is easy
once you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-the-staged-model-repository-framework}{created
staged model repository implementation classes}.
You'll step through a quick example to demonstrate leveraging the
\texttt{StagedModelRepository} interface in a staged model data handler.
The code snippets originate from Liferay's Bookmarks app and Bookmarks
Entries.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a getter and setter method to make a
\texttt{StagedModelRepository} object available for the
\texttt{BookmarksEntry} entity:
\begin{verbatim}
@Override
protected StagedModelRepository getStagedModelRepository() {
return _stagedModelRepository;
}
@Reference(
target = "(model.class.name=com.liferay.bookmarks.model.BookmarksEntry)",
unbind = "-"
)
protected void setStagedModelRepository(
StagedModelRepository stagedModelRepository) {
_stagedModelRepository = stagedModelRepository;
}
private StagedModelRepository _stagedModelRepository;
\end{verbatim}
\item
Call your \texttt{\_stagedModelRepository} object to leverage its
specialized export/import logic. For example,
\begin{verbatim}
newEntry = _stagedModelRepository.updateStagedModel(portletDataContext, importedEntry);
\end{verbatim}
Without the staged model repository logic, you would've called your
local service like this:
\begin{verbatim}
serviceContext.setUuid(entry.getUuid());
newEntry = _bookmarksEntryLocalService.addEntry(
userId, portletDataContext.getScopeGroupId(), folderId, entry.getName(), entry.getUrl(), entry.getDescription(), serviceContext);
\end{verbatim}
The large number of parameters and UUID setter the local service
method requires aren't needed when leveraging the staged model
repository.
\end{enumerate}
Great! You've successfully leveraged your staged model repository from a
data handler!
\chapter{Using the Export/Import Lifecycle Listener
Framework}\label{using-the-exportimport-lifecycle-listener-framework}
In this tutorial, you'll learn how to use the
\texttt{ExportImportLifecycleListener} framework to listen for
processes/events during the staging and export/import lifecycles.
To begin creating your lifecycle listener, you must create a module.
Follow the steps below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
an OSGi module}.
\item
Create a unique package name in the module's \texttt{src} directory
and create a new Java class in that package. To follow naming
conventions, begin the class name with the entity or action name
you're processing, followed by \emph{ExportImportLifecycleListener}
(e.g., \texttt{LoggerExportImportLifecycleListener}).
\item
You must extend one of the two Base classes provided with the
Export/Import Lifecycle Listener framework:
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lifecycle/BaseExportImportLifecycleListener.html}{BaseExportImportLifecycleListener}
(event listener) or
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lifecycle/BaseProcessExportImportLifecycleListener.html}{BaseProcessExportImportLifecycleListener}
(process listener). To choose, you'll need to consider what parts of a
lifecycle you want to listen for (event or process).
\item
Directly above the class's declaration, insert the following
annotation:
\begin{verbatim}
@Component(immediate = true, service = ExportImportLifecycleListener.class)
\end{verbatim}
This annotation declares the implementation class of the component and
specifies that the portal should start the module immediately.
\item
Specify the methods you want to implement in your class. As an
example, you'll step through the
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/exportimport/lifecycle/LoggerExportImportLifecycleListener.html}{LoggerExportImportLifecycleListener}.
This listener extends the \texttt{BaseExportImportLifecycleListener},
so you immediately know that it deals with lifecycle events.
\item
Add the \texttt{getStagedModelLogFragment(...)} method:
\begin{verbatim}
protected String getStagedModelLogFragment(StagedModel stagedModel) {
StringBundler sb = new StringBundler(8);
sb.append(StringPool.OPEN_CURLY_BRACE);
sb.append("class: ");
sb.append(ExportImportClassedModelUtil.getClassName(stagedModel));
if (stagedModel instanceof StagedGroupedModel) {
StagedGroupedModel stagedGroupedModel =
(StagedGroupedModel)stagedModel;
sb.append(", groupId: ");
sb.append(stagedGroupedModel.getGroupId());
}
sb.append(", uuid: ");
sb.append(stagedModel.getUuid());
sb.append(StringPool.CLOSE_CURLY_BRACE);
return sb.toString();
}
\end{verbatim}
This retrieves the staged model's log fragment, which is the lifecycle
listener's logging information on events.
\item
Add the \texttt{isParallel()} method:
\begin{verbatim}
@Override
public boolean isParallel() {
return false;
}
\end{verbatim}
This determines whether your listener should run in parallel with the
import/export process, or if the calling method should stop, execute
the listener, and return to where the event was fired after the
listener has finished.
\item
Add the \texttt{onExportImportLifecycleEvent(...)} method:
\begin{verbatim}
@Override
public void onExportImportLifecycleEvent(
ExportImportLifecycleEvent exportImportLifecycleEvent)
throws Exception {
if (!_log.isDebugEnabled()) {
return;
}
super.onExportImportLifecycleEvent(exportImportLifecycleEvent);
}
\end{verbatim}
This consumes the lifecycle event and passes it through the base
class's method (as long as Debug mode is not enabled).
\item
Each remaining method is called to print logging information for the
user. For example, when a layout export fails, logging information
directly related to that event is printed:
\begin{verbatim}
@Override
protected void onLayoutExportFailed(
PortletDataContext portletDataContext, Throwable throwable)
throws Exception {
if (!_log.isDebugEnabled()) {
return;
}
_log.debug(
"Layout export failed for group " + portletDataContext.getGroupId(),
throwable);
}
\end{verbatim}
In summary, the \texttt{LoggerExportImportLifecycleListener} uses the
lifecycle listener framework to print messages to the log when an
export/import event occurs. You can view the other logging methods
implemented for this class
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-m2/modules/apps/export-import/export-import-service/src/main/java/com/liferay/exportimport/lifecycle/LoggerExportImportLifecycleListener.java}{here}.
\item
Once you've successfully created your export/import lifecycle listener
module, generate the module's JAR file and copy it to Liferay DXP's
\texttt{osgi/modules} folder.
Once your module is installed and activated in your instance's service
registry, your lifecycle listener is ready for use in your Portal
instance.
\end{enumerate}
Terrific! You learned about the Export/Import Lifecycle Listener
framework, and you've learned how to create your own listener for
events/processes that occur during export/import of your portal's
content.
\chapter{Initiating New Export/Import
Processes}\label{initiating-new-exportimport-processes}
In this tutorial, you'll learn about the
\texttt{ExportImportConfiguration} framework and how you can take
advantage of provided services and factories to create these controller
objects. Once they're created, you can easily implement whatever
import/export functionality you need.
Your first step is to create an \texttt{ExportImportConfiguration}
object and use it to initiate your custom export/import or staging
process.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/configuration/ExportImportConfigurationSettingsMapFactory.html}{\texttt{ExportImportConfigurationSettingsMapFactory}}
class to create a layout export settings map:
\begin{verbatim}
Map exportLayoutSettingsMap =
ExportImportConfigurationSettingsMapFactory.
buildExportLayoutSettingsMap(...);
\end{verbatim}
\item
Create the \texttt{ExportImportConfiguration} object by using an
\emph{add} method in the entity's local service. The map created
previously is used as a parameter to create the
\texttt{ExportImportConfiguration} object.
\begin{verbatim}
ExportImportConfiguration exportImportConfiguration =
exportImportConfigurationLocalService.
addDraftExportImportConfiguration(
user.getUserId(),
ExportImportConfigurationConstants.TYPE_EXPORT_LAYOUT,
exportLayoutSettingsMap);
\end{verbatim}
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/service/ExportImportConfigurationLocalService.html}{ExportImportConfigurationLocalService}
provides several useful methods to create and modify your custom
\texttt{ExportImportConfiguration}.
\item
Call the appropriate service using your newly created
\texttt{ExportImportConfiguration} object to initiate an export/import
or staging process. For example,
\begin{verbatim}
files[0] = exportImportLocalService.exportLayoutsAsFile(
exportImportConfiguration);
\end{verbatim}
Notice that your \texttt{ExportImportConfiguration} object is the only
needed parameter in the method. Your configuration object holds all
the required parameters and settings necessary to export your layouts
from Liferay DXP.
\end{enumerate}
It's that easy! To start your own export/import or staging process, you
must create an \texttt{ExportImportConfiguration} object using a
combination of the three provided \texttt{ExportImportConfiguration}
factories (outlined
\href{/docs/7-2/frameworks/-/knowledge_base/f/export-import\#exportimport-processes}{here}).
Once you have your configuration object, provide it as a parameter in
one of the many service methods available to you by the Export/Import or
Staging interfaces to begin your desired process.
\chapter{Staging}\label{staging-1}
\href{docs/7-2/user/-/knowledge_base/u/staging-content-for-publication}{Staging}
lets users change a Site without affecting the live Site and then
publish all the changes in one fell swoop. If you include staging
support in your application, your users can stage its content until it's
ready.
For example, if your application uses the Staging framework and provides
information intended only during a specific holiday, users can save your
application's assets specific for that holiday. They reside in the
Staging environment until they're ready for publishing.
Staging and Export/Import share the same base framework. When publishing
your staged content to the live Site, you're essentially importing
content from the staged Site and exporting it to the live Site. This
means that implementing Staging in your app is \emph{almost} the same as
implementing the Export/Import framework. You can visit the
\href{/docs/7-2/frameworks/-/knowledge_base/f/export-import}{Export/Import}
framework's articles for the base APIs that both it and the Staging
frameworks share.
If your app supports Export/Import, its entities
(\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-staged-models}{staged
models}) are automatically tracked by Staging with the use of
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-data-handlers}{data
handlers}. There are some Staging-specific configurations you can add
that are not shared by Export/Import. Some Staging-specific actions you
can complete include
\begin{itemize}
\tightlist
\item
Control Staging UI settings
\item
Filter Staging-specific processes
\item
Check for Staging-specific states
\end{itemize}
You'll learn about these next.
\section{Controlling Staging's UI
Settings}\label{controlling-stagings-ui-settings}
You can control most of Staging's UI from your portlet data handler.
This can be done several ways; first, you can configure predefined
setter methods in the portlet data handler's \texttt{activate()} method:
\begin{itemize}
\tightlist
\item
\texttt{setStagingControls}: adds fine grained controls over staging
behavior that is rendered in the Staging UI. For example, this enables
your app's checkboxes in the Content section of the Publication
screen. This is usually set like this:
\texttt{setStagingControls(getExportControls());}. The staging UI
typically provides the same content as the export UI (i.e., the
Content section for selecting what to publish/export), so it leverages
its UI. You can set the Staging UI differently by configuring the
\texttt{setStagingControls} method differently. See the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/asset/asset-tags-service/src/main/java/com/liferay/asset/tags/internal/exportimport/data/handler/AssetTagsPortletDataHandler.java\#L82-L84}{\texttt{AssetTagsPortletDataHandler}}
class for an example of not copying the Export UI for the Staging UI.
\item
\texttt{setDataAlwaysStaged}: defines whether you can enable/disable
your app's content staging (i.e., selectable from the Publication
screen). For example, setting this method to \texttt{true}
automatically stages your app's content. Users can no longer choose
whether its content should be staged.
\end{itemize}
Other setter methods are available that control both Export/Import and
Staging settings. You can reference them by visiting the
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-data-handlers\#understanding-the-portletdatahandler-interface}{Understanding
the \texttt{PortletDataHandler} Interface} section.
You can also control whether your app is enabled on the Staged Content
screen by adding this method to your portlet data handler:
\begin{verbatim}
@Override
public boolean isConfigurationEnabled() {
return false;
}
\end{verbatim}
When this is set to \texttt{false}, your app is disabled on the Staged
Content screen. This is set to \texttt{true} by default.
\begin{figure}
\centering
\includegraphics{./images/staged-content-screen.png}
\caption{There are many apps available to select from the Staged Content
screen.}
\end{figure}
The majority of Staging-specific configurations are completed in a
portlet data handler. The staged model data handler does come into play
when you want to filter for certain staging processes/states. You'll
learn about this next.
\section{Filtering Staging-Specific Processes and
States}\label{filtering-staging-specific-processes-and-states}
You can filter for certain staging-specific processes/states and
complete actions based on the returned status. You can do this by
leveraging the following classes from a staged model data handler:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/exportimport/kernel/lar/ExportImportThreadLocal.html}{\texttt{ExportImportThreadLocal}}
\item
\href{https://docs.liferay.com/dxp/apps/staging/latest/javadocs/com/liferay/staging/StagingGroupHelper.html}{\texttt{StagingGroupHelper}}
\end{itemize}
The \texttt{ExportImportThreadLocal} class provides boolean methods that
return whether a specific process is in progress. Use this to check for
events affecting the entire site. For example, you can check if the
following processes are in progress:
\begin{itemize}
\tightlist
\item
Local Staging
\item
Remote Staging
\item
Layout Validation
\item
Portlet Staging
\item
etc.
\end{itemize}
The \texttt{StagingGroupHelper} interface provides utility methods that
return the staging state in your app. This is intended to check for
events only affecting your app. For example, you can check if your app
is in these states:
\begin{itemize}
\tightlist
\item
Resides in Local Staging group
\item
Resides in Remote Live group
\item
Is a staged portlet
\item
etc.
\end{itemize}
A real example filtering for a staging process and state can be found in
the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-b2/modules/apps/asset/asset-list-service/src/main/java/com/liferay/asset/list/internal/exportimport/data/handler/AssetListEntryStagedModelDataHandler.java\#L215-L222}{\texttt{AssetListEntryStagedModelDataHandler}}
class:
\begin{verbatim}
if ((assetRendererFactory != null) &&
ExportImportThreadLocal.isStagingInProcess() &&
!_stagingGroupHelper.isStagedPortlet(
assetEntry.getGroupId(),
assetRendererFactory.getPortletId())) {
continue;
}
\end{verbatim}
The staged model data handler uses the
\texttt{ExportImportThreadLocal.isStagingInProcess()} method to verify
that a staging process is running. It also checks whether the app is
staged by executing \texttt{!\_stagingGroupHelper.isStagedPortlet(...)}.
Excellent! You can now filter for staging-specific processes and states.
\chapter{Dependency Injection}\label{dependency-injection}
When you're using a object based on its interface, you don't have to
concern yourself with the implementation because it's abstracted from
you. At runtime, the implementation used depends on your environment and
your app's configuration. Liferay DXP offers several standard ways to
register implementations and inject them into your applications.
\textbf{Contexts and Dependency Injection (CDI):} Is the Java EE
standard dependency injection mechanism. Liferay DXP's CDI bean
container makes an application's concrete classes available as beans.
Bean classes can user other beans by way of injecting them into their
fields that have the \texttt{@Inject} annotation.
\textbf{OSGi Declarative Services:} Liferay DXP's OSGi runtime framework
allows components to register as service provides and other components
can call on the registry to bind the services to their fields that have
the \texttt{@Reference} annotation. Liferay DXP's services and services
you create using
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} are available as OSGi Declarative Services.
\textbf{Spring DI:} The Spring framework includes inversion of control
(IoC) and dependency injection. It's available to applications that
configure the Spring framework.
As an added bonus, Liferay DXP provides
\href{/docs/7-2/frameworks/-/knowledge_base/f/osgi-cdi-integration}{OSGi
CDI integration}. It lets you publish CDI beans as OSGi services and
consume OSGi services in your CDI beans. Which dependency injection
mechanism will you use? Read on to learn more about them.
\chapter{CDI Dependency Injection}\label{cdi-dependency-injection}
Portlet 3.0 (see \href{https://jcp.org/en/jsr/detail?id=362}{JSR 362})
supports Contexts and Dependency Injection (CDI) so you can create and
use injectable classes (CDI beans) in your portlet. It also provides
injectable portlet artifacts called
\href{/docs/7-2/reference/-/knowledge_base/r/cdi-portlet-predefined-beans}{Portlet
Predefined Beans}. They give a portlet's CDI beans access to the portlet
configuration, preferences, requests, responses, and more. Here's how to
create and use CDI beans and Portlet Predefined Beans:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a portlet WAR project, if you haven't created one already.
\begin{itemize}
\tightlist
\item
Any project that has a class that implements the
\href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/Portlet.html}{\texttt{javax.portlet.Portlet}}
interface, either directly or indirectly.
\end{itemize}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** If you're developing a portlet JAR, such as a
[Liferay MVC Portlet](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet),
use CDI via
[OSGi CDI Integration](/docs/7-2/frameworks/-/knowledge_base/f/osgi-cdi-integration).
\end{verbatim}
\noindent\hrulefill
\noindent\hrulefill
\begin{verbatim}
**Note:**
Liferay DXP exports the packages provided by the Portlet API and CDI API.
Liferay project templates typically include them as transitive
dependencies. If you must explicitly depend on the portlet API and CDI
artifacts, add them as `compileOnly` (Gradle) or `provided` (Maven)
dependencies.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
If your portlet WAR project isn't a Bean Portlet, add this
\texttt{src/main/webapp/WEB-INF/beans.xml} file to it. This file tells
CDI to scan the project for CDI annotations.
\begin{verbatim}
\end{verbatim}
\item
Add the
\href{https://docs.oracle.com/javaee/7/api/javax/enterprise/context/ApplicationScoped.html}{\texttt{@ApplicationScoped}}
annotation to your portlet class.
\begin{verbatim}
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class MyPortlet ... {
...
}
\end{verbatim}
\item
Make sure all concrete classes you want to make injectable have the
default constructor. These classes are now CDI beans.
\item
Add a scope to each CDI bean.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
Bean Scope | Description |
----------------------- | ---------------- |
[`@ApplicationScoped`](https://docs.oracle.com/javaee/7/api/javax/enterprise/context/ApplicationScoped.html) | Shares the bean's state across all users' interactions with the portlet. |
[`@Dependent`](https://docs.oracle.com/javaee/7/api/javax/enterprise/context/Dependent.html) | (default scope) Designates the bean to be for the client bean and share the client bean's lifecycle. |
[`@PortletRequestScoped`](https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/PortletRequestScoped.html) | Associates the bean with the portlet request. |
[`@PortletSessionScoped`](https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/PortletSessionScoped.html) | Places the bean in the portlet session. |
[`@RenderStateScoped`](https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/RenderStateScoped.html) | Stores the bean as part of the portlet's render state. **Important:** The bean must implement the `PortletSerializable` interface. |
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\item
Use the \href{https://jcp.org/en/jsr/detail?id=330}{JSR 330}
\href{https://docs.oracle.com/javaee/7/api/javax/inject/Inject.html}{\texttt{@Inject}}
annotation in a CDI bean to inject another CDI bean into it. For
example, this code informs Liferay DXP's CDI bean container to inject
a \texttt{GuestBook} CDI bean into this \texttt{guestbook} field.
\begin{verbatim}
@Inject
private GuestBook guestbook;
\end{verbatim}
\item
Inject any
\href{/docs/7-2/reference/-/knowledge_base/r/cdi-portlet-predefined-beans}{Portlet
Predefined Beans} (portlet request scoped or dependent scoped beans)
into your \texttt{@PortletRequestScoped} CDI beans.
\begin{verbatim}
@PortletRequestScoped
public class RequestProcessor ... {
@Inject
private PortletRequest portletRequest;
...
}
\end{verbatim}
\item
Inject any
\href{/docs/7-2/reference/-/knowledge_base/r/cdi-portlet-predefined-beans}{dependent
scoped Portlet Predefined Beans} into your \texttt{ApplicationScoped}
or \texttt{@Dependent} scoped CDI beans. For example,
\begin{verbatim}
@ApplicationScoped
public class MyPortlet ... {
@Inject
private PortletConfig portletConfig;
...
}
\end{verbatim}
\item
Use bean EL names to reference any
\href{/docs/7-2/reference/-/knowledge_base/r/cdi-portlet-predefined-beans}{portlet
redefined \emph{named beans}} in your JSP or JSF pages.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy}
your project.
\end{enumerate}
Congratulations! You have created and used CDI beans and Portlet
Predefined Beans in your portlet.
\section{Related Topics}\label{related-topics-47}
\href{/docs/7-2/reference/-/knowledge_base/r/cdi-portlet-predefined-beans}{CDI
Portlet Predefined Beans}
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
\chapter{OSGi CDI Integration}\label{osgi-cdi-integration}
Liferay DXP's runtime environment consists of services (OSGi services).
The OSGi service registry and Service Component Runtime (SCR) facilitate
providing and consuming services.
\href{http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html}{Contexts and
Dependency Injection (CDI)} is a Java SE and EE standard for lifecycle
events, stateful objects, and dependency injection.
\href{https://osgi.org/specification/osgi.enterprise/7.0.0/service.cdi.html}{OSGi
CDI Integration} brings features and capabilities of CDI to OSGi and
makes OSGi services available to CDI beans. Here you'll learn how to
\begin{itemize}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/publishing-cdi-beans-as-osgi-services}{Publish
CDI beans as OSGi services}: Register CDI beans as services you can
use to customize Liferay DXP components.
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-osgi-services-in-a-bean}{Use
OSGi services in beans}: Leverage any OSGi service published on
Liferay DXP in any bean.
\end{itemize}
The following use cases provide more detail.
\section{Use Case: Registering a CDI bean as an OSGi
service}\label{use-case-registering-a-cdi-bean-as-an-osgi-service}
Liferay DXP extension points are implemented as OSGi services. If
there's a piece of functionality you must customize, you don't have to
learn OSGi to do it: you can write your extension/override as a CDI bean
instead and use OSGi CDI integration to publish your bean as an OSGi
service.
By implementing the service in your CDI bean class and adding the
integration's \texttt{@org.osgi.service.cdi.annotations.Service}
annotation to it, your bean registers as providing that OSGi service. In
this way, service consumers can use your service implementation (i.e.,
your CDI bean).
For example, the Service Registry in figure 1 shows two implementations
of an OSGi service called \texttt{S1}:
\begin{figure}
\centering
\includegraphics{./images/injecting-bean-osgi-service.png}
\caption{OSGi Service Component Runtime (SCR) finds \texttt{MyBean} as
the best (highest ranked) \texttt{S1} service provider and binds it to
consumer component \texttt{C1}.}
\end{figure}
\begin{itemize}
\item
\texttt{MyBean} is a CDI bean whose service rank is \texttt{1000}.
\item
\texttt{MySvcImpl} has a service rank of \texttt{0}.
\end{itemize}
The Service Component Runtime (\texttt{SCR}) finds the matching, highest
ranked \texttt{S1} service provider and binds it to consumer
\texttt{C1}. The fact that \texttt{MyBean} is a CDI bean is transparent
to the SCR.
Once a CDI bean is registered as a service, components can use it as
they would any other OSGi service.
\section{Use Case: Using an OSGi service in a
bean}\label{use-case-using-an-osgi-service-in-a-bean}
Liferay DXP contains many development frameworks for all of its
constructs, such as Users, Sites, Documents, Comments, and the APIs for
these assets are all implemented as OSGi services. When developers write
new applications using Liferay's development frameworks, new assets
become available and integrated with the rest of the system. OSGi CDI
integration enables your beans to access these OSGi services.
\begin{figure}
\centering
\includegraphics{./images/using-a-service-in-a-bean.png}
\caption{Here how Liferay's \texttt{UserLocalService} is injected into a
bean.}
\end{figure}
In figure 2, for example, CDI bean \texttt{SomeBean} uses the OSGi CDI
integration annotation
\texttt{@org.osgi.service.cdi.annotations.Reference} (along with CDI
annotation \texttt{@Inject}) to inject the OSGi service
\texttt{UserLocalService}. The Service Component Runtime (\texttt{SCR})
finds the \texttt{UserLocalService} in the Service Registry and binds it
to \texttt{SomeBean}'s field \texttt{userSvc}.
These are the most common use cases, but you might have more. Now you
can get started using OSGi CDI integration to
\href{/docs/7-2/frameworks/-/knowledge_base/f/publishing-cdi-beans-as-osgi-services}{publish
CDI beans as OSGi services}!
\chapter{Publishing CDI Beans as OSGi
Services}\label{publishing-cdi-beans-as-osgi-services}
You can publish CDI beans as OSGi services, making them accessible via
the Liferay's OSGi service registry. Here's how:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add a project dependency on the OSGi CDI Integration artifact. For
example, here's the dependency to use in a Maven \texttt{pom.xml}
file:
\begin{verbatim}
org.osgi
org.osgi.service.cdi
1.0.0
\end{verbatim}
\item
Make your CDI bean implement the service interface you're providing.
For example, \texttt{ShopImpl} provides the \texttt{Shop} service by
implementing that interface.
\begin{verbatim}
package my.package;
public class ShopImpl implements Shop {
...
}
\end{verbatim}
\item
Annotate your CDI bean class with
\texttt{@org.osgi.service.cdi.annotations.Service}.
\begin{verbatim}
package my.package;
import org.osgi.service.cdi.annotations.Service;
@Service
public class ShopImpl implements Shop {
...
}
\end{verbatim}
\item
Deploy the API that defines the service interface, if you haven't
deployed it already.
\item
Build and deploy your service project bundle.
\end{enumerate}
Once your bundle installs and activates, your bean's service
implementation is available. You can use Gogo Shell commands can verify
that the service registered.
For example, here are steps for verifying that a bundle
\texttt{com.liferay.portal.samples.cdi.jar.portlet} registers a service
called \texttt{org.apache.portals.samples.Users}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Gogo
Shell}.
\item
Use the \texttt{lb} Gogo command and \texttt{grep} (pass in the
bundle's symbolic name) to find your bundle (and its ID).
Example command:
\begin{verbatim}
g!: lb | grep com.liferay.portal.samples.cdi.jar.portlet
\end{verbatim}
Results:
\begin{verbatim}
924|Active | 10|com.liferay.portal.samples.cdi.jar.portlet (0.0.1.201901252134)|0.0.1.201901252134
\end{verbatim}
The first column contains the bundle ID.
\item
Use the \texttt{b} Gogo command with your bundle ID to list your
bundle's details and verify the bundle includes your service as one of
its registered services.
Example command:
\begin{verbatim}
g!: b 924
\end{verbatim}
Results:
\begin{verbatim}
com.liferay.portal.samples.cdi.jar.portlet_0.0.1.201901252134 [924]
Id=924, Status=ACTIVE Data Root=C:\git\bundles\osgi\state\org.eclipse.osgi\924\data
"Registered Services"
...
{org.apache.portals.samples.Users}={osgi.command.scope=cdiportlet, service.id=4232, service.bundleid=924, service.scope=singleton, osgi.command.function=[getUsersCount], component.name=com.liferay.portal.samples.cdi.jar.portlet, component.id=1}
...
\end{verbatim}
\end{enumerate}
Congratulations on publishing your CDI bean as an OSGi service!
\chapter{Using OSGi Services in a
Bean}\label{using-osgi-services-in-a-bean}
Any bean can use the
\texttt{@org.osgi.service.cdi.annotations.Reference} annotation to
inject OSGi services. It's the easiest way for a bean to access an OSGi
service. Here's how:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add a project dependency on the OSGi CDI Integration artifact. For
example, here's the dependency to use in a Maven \texttt{pom.xml}
file:
\begin{verbatim}
org.osgi
org.osgi.service.cdi
1.0.0
\end{verbatim}
\item
Obtain and inject the OSGi service by using the
\texttt{@org.osgi.service.cdi.annotations.Reference} and
\texttt{@javax.inject.Inject} annotations respectively. Here's an
example of injecting a service of type \texttt{ProductStore}.
\begin{verbatim}
import javax.inject.Inject;
import org.osgi.service.cdi.annotations.Reference;
import package.path.ProductStore;
public class MyBean {
@Inject
@Reference
ProductStore productStore;
...
}
\end{verbatim}
\item
Deploy your bean project to Liferay DXP.
\end{enumerate}
Congratulations on injecting an OSGi service into your bean! Now your
bean uses the OSGi service you injected.
\chapter{Declarative Services}\label{declarative-services}
Liferay DXP's OSGi framework registers objects as \emph{services}. Each
service offers functionality and can leverage functionality other
services provide. The OSGi Services model supports a collaborative
environment for objects.
Declarative Services (DS) provides a service component model on top of
OSGi Services. DS service components are marked with the
\href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}}
annotation and implement or extend a service class. Service components
can refer to and use each other's services. The Service Component
Runtime (SCR) registers component services and handles binding them to
other components that reference them.
Here's how the ``magic'' happens:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\textbf{Service registration:} On installing a module that contains a
service component, the SCR creates a component configuration that
associates the component with its specified service type and stores it
in a service registry.
\item
\textbf{Service reference handling:} On installing a module whose
service component references another service type, the SCR searches
the registry for a component configuration that matches the service
type and on finding a match binds an instance of that service to the
referring component.
\end{enumerate}
It's publish, find, and bind at its best!
How do you use DS to register and bind services? Does it involve
creating XML files? No, it's much easier than that. You use two
annotations: \texttt{@Component} and \texttt{@Reference}.
\begin{itemize}
\item
\href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}}:
Add this annotation to a class definition to make the class a
component--a service provider.
\item
\href{https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Reference.html}{\texttt{@Reference}}:
Add this annotation to a field to inject it with a service that
matches the field's type.
\end{itemize}
The
\href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}}
annotation makes the class an OSGi component. Setting the annotation's
\texttt{service} attribute to a particular service type allows other
components to reference the service component by that service type.
For example, the following class is a service component of type
\texttt{SomeApi.class}.
\begin{verbatim}
@Component(
service = SomeApi.class
)
public class Service1 implements SomeApi {
...
}
\end{verbatim}
On deploying this class's module, the SCR creates a component
configuration that associates the class with the service type
\texttt{SomeApi}.
Specifying a service reference is easy too. Applying the
\href{https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Reference.html}{\texttt{@Reference}}
annotation to a field marks it to be injected with a service matching
the field's type.
\begin{verbatim}
@Reference
SomeApi _someApi;
\end{verbatim}
On deploying this class's module, the SCR finds a component
configuration of the class type \texttt{SomeApi} and binds the service
to this referencing component class's field \texttt{SomeApi\ \_someApi}.
\noindent\hrulefill
\textbf{Note:} The \texttt{@Reference} annotation can only be used in a
class that is annotated with \texttt{@Component} (i.e, a Declarative
Services component ) or a bean class that uses OSGi CDI integration.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Note:} At build time in modules created from
\href{/docs/7-1/reference/-/knowledge_base/r/project-templates}{Liferay
project templates}, bnd creates a \emph{component description} file for
each module's components automatically. The file specifies the
component's services, dependencies, and activation characteristics. On
module deployment, the OSGi framework reads the component description to
create the component and manage its dependency on other components.
\noindent\hrulefill
The SCR stands ready to pair service components with each other. For
each referencing component, the SCR binds an instance of the targeted
service to it.
As an improvement over dependency injection with Spring, OSGi
Declarative Services supports dynamic dependency injection. You can
create and publish service components for other classes to use. You can
update the components and even publish alternative component
implementations for a service. This kind of dynamism is a powerful part
of Liferay DXP.
\chapter{Service Trackers for OSGi
Services}\label{service-trackers-for-osgi-services}
In an OSGi runtime ecosystem, you must consider how your apps can rely
on OSGi services in other modules for functionality. It's possible for
service implementations to be swapped out or removed entirely, and your
app must not just survive but thrive in this environment.
If you want to
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{call
OSGi services from an OSGi Declarative Services \texttt{@Component}}
classes, it's easy: you just use a
\href{https://osgi.org/specification/osgi.cmpn/7.0.0/service.component.html}{Declarative
Services (DS)} annotation,
\href{ttps://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Reference.html}{\texttt{@Reference}},
to inject the service. The component activates when the referenced
service is available.
\noindent\hrulefill
\textbf{Note:} The \texttt{@Reference} annotation can only be used in a
class that is annotated with
\href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}}.
That is, only a Declarative Services component can use
\texttt{@Reference} to bind to an OSGi service.
\noindent\hrulefill
If you want to call an OSGi service from a bean, use
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-osgi-services-in-a-bean}{OSGi
CDI integration}.
DS \texttt{@Reference} with \texttt{@Component}s and OSGi CDI
integration with beans manage much of the complexity of service dynamism
for you transparently. If you can use either of them, you should.
Otherwise, read
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{implement
a Service Tracker} to look up services in the service registry.
\chapter{Using a Service Tracker}\label{using-a-service-tracker}
Your non-OSGi and non-bean classes can access any service registered in
the OSGi runtime using a Service Tracker. It lets you access any OSGi
services including your own
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder services} and the services published by Liferay's modules (like
the popular \texttt{UserLocalService}).
You can create a service tracker in two ways:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a service tracker where you need it.
\item
Create a class that extends
\texttt{org.osgi.util.tracker.ServiceTracker}.
\item
Create a service tracker that tracks service events using a callback
handler.
\end{enumerate}
Both ways depend on \texttt{org.osgi.core}, whose packages Liferay DXP
exports by default. Configure it as \texttt{compileOnly} (Gradle) or
\texttt{provided} (Maven). See the
\href{/docs/7-1/reference/-/knowledge_base/r/third-party-packages-portal-exports}{Third
Party Packages Portal Exports} for more information.
\noindent\hrulefill
\textbf{Note:} The static utility classes (e.g.,
\texttt{UserLocalServiceUtil}) that were useful in Liferay Portal 6.2
(and earlier) exist for compatibility but should not be called, if
possible. Static utility classes cannot account for the OSGi runtime's
dynamic environment. If you use a static class, you might attempt
calling a stopped service or one that hasn't been deployed or started.
This could cause unrecoverable runtime errors. Service Trackers,
however, help you make OSGi-friendly service calls.
\noindent\hrulefill
\section{Creating a New Service Tracker Where You Need
It}\label{creating-a-new-service-tracker-where-you-need-it}
To create it directly, do this:
\begin{verbatim}
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.tracker.ServiceTracker;
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
BundleContext bundleContext = bundle.getBundleContext();
ServiceTracker serviceTracker =
new ServiceTracker(bundleContext, SomeService.class, null);
serviceTracker.open();
SomeService someService = serviceTracker.waitForService(500);
\end{verbatim}
\section{Create a Class That Extends
ServiceTracker}\label{create-a-class-that-extends-servicetracker}
A better way is to create a class that extends
\texttt{org.osgi.util.tracker.ServiceTracker}, because this simplifies
your code.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a class like this one that extends \texttt{ServiceTracker}:
\begin{verbatim}
public class SomeServiceTracker
extends ServiceTracker {
public SomeServiceTracker(Object host) {
super(
FrameworkUtil.getBundle(host.getClass()).getBundleContext(),
SomeService.class, null);
}
}
\end{verbatim}
\item
Construct a new instance of your service tracker where you need it.
The \texttt{Object\ host} parameter obtains your own bundle context
and must be an object from your own bundle in order to give accurate
results.
\begin{verbatim}
ServiceTracker someServiceTracker =
new SomeServiceTracker(this);
\end{verbatim}
\item
When you want to use the service tracker, open it, typically as early
as you can.
\begin{verbatim}
someServiceTracker.open();
\end{verbatim}
\item
Before attempting to use a service, use the Service Tracker to
interrogate the service's state. For example, check whether the
service is \texttt{null}:
\begin{verbatim}
SomeService someService = someServiceTracker.getService();
if (someService == null) {
_log.warn("The required service 'SomeService' is not available.");
}
else {
someService.doSomethingCool();
}
\end{verbatim}
\end{enumerate}
Note, service trackers have several other utility functions for
introspecting tracked services.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\item
Later when your application is being destroyed or undeployed, close
the service tracker.
\begin{verbatim}
someServiceTracker.close();
\end{verbatim}
\end{enumerate}
If you need to track multiple services or their events, implement a
service tracker that uses callback handlers.
\section{Creating a Service Tracker that Tracks Service Events Using a
Callback
Handler}\label{creating-a-service-tracker-that-tracks-service-events-using-a-callback-handler}
If there's a strong possibility the service might not be available or if
you need to track multiple services, the Service Tracker API provides a
callback mechanism that operates on service \emph{events}. To use this,
override \texttt{ServiceTracker}'s \texttt{addingService} and
\texttt{removedService} methods. Their \texttt{ServiceReference}
parameter references an active service object.
Here's an example \texttt{ServiceTracker} implementation from the
\href{https://osgi.org/specification/osgi.core/7.0.0/util.tracker.html\#d0e51991}{OSGi
Alliance's OSGi Core Release 7 specification}:
\begin{verbatim}
new ServiceTracker(context, HttpService.class, null) {
public MyServlet addingService(ServiceReference reference) {
HttpService httpService = context.getService(reference);
MyServlet myServlet = new MyServlet(httpService);
return myServlet;
}
public void removedService(
ServiceReference reference, MyServlet myServlet) {
myServlet.close();
context.ungetService(reference);
}
}
\end{verbatim}
When the \texttt{HttpService} is added to the OSGi registry, this
\texttt{ServiceTracker} creates a new wrapper class, \texttt{MyServlet},
which uses the newly added service. When the service is removed from the
registry, the \texttt{removedService} method cleans up related
resources.
As an alternative to directly overloading \texttt{ServiceTracker}
methods, create a
\texttt{org.osgi.util.tracker.ServiceTrackerCustomizer}:
\begin{verbatim}
class MyServiceTrackerCustomizer
implements ServiceTrackerCustomizer {
private final BundleContext bundleContext;
MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyWrapper addedService(
ServiceReference serviceReference) {
// Determine if the service is one that's interesting to you.
// The return type of this method is the `tracked` type. Its type
// is what is returned from `getService*` methods; useful for wrapping
// the service with your own type (e.g., MyWrapper).
if (isInteresting(serviceReference)) {
MyWrapper myWrapper = new MyWrapper(
serviceReference, bundleContext.getService());
// trigger the logic that requires the available service(s)
triggerServiceAddedLogic(myWrapper);
return myWrapper;
}
// If the return is null, the tracker is effectively ignoring any further
// events for the service reference
return null;
}
@Override
public void modifiedService(
ServiceReference serviceReference, MyWrapper myWrapper) {
// handle the modified service
}
@Override
public void removedService(
ServiceReference serviceReference, MyWrapper myWrapper) {
// finally, trigger logic when the service is going away
triggerServiceRemovedLogic(myWrapper);
}
}
\end{verbatim}
Register the \texttt{ServiceTrackerCustomizer} by passing it as the
\texttt{ServiceTracker} constructor's third parameter.
\begin{verbatim}
ServiceTrackerCustomizer serviceTrackerCustomizer =
new MyServiceTrackerCustomizer();
ServiceTracker serviceTracker =
new ServiceTracker<>(
bundleContext, SomeService.class, serviceTrackerCustomizer);
\end{verbatim}
Using service trackers requires producing some boilerplate code, but now
you can look up services in the service registry, even if your plugins
can't take advantage of the Declarative Services component model.
\chapter{Friendly URLs}\label{friendly-urls}
This is a story of two URLs who couldn't be more different. One was full
of himself and always wanted to show everyone (users and SEO services
alike) just how smart he was by openly displaying all the parameters he
carried. He was happiest when he could tell people he met were
intimidated and confused by him.
\begin{verbatim}
http://localhost:8080/group/guest/~/control_panel/manage?p_p_id=com_liferay_blogs_web_portlet_BlogsAdminPortlet&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&_com_liferay_blogs_web_portlet_BlogsAdminPortlet_mvcRenderCommandName=%2Fblogs%2Fedit_entry&_com_liferay_blogs_web_portlet_BlogsAdminPortlet_redirect=http%3A%2F%2Flocalhost%3A8080%2Fgroup%2Fguest%2F~%2Fcontrol_panel%2Fmanage%3Fp_p_id%3Dcom_liferay_blogs_web_portlet_BlogsAdminPortlet%26p_p_lifecycle%3D0%26p_p_state%3Dmaximized%26p_p_mode%3Dview%26_com_liferay_blogs_web_portlet_BlogsAdminPortlet_mvcRenderCommandName%3D%252Fblogs%252Fview%26_com_liferay_blogs_web_portlet_BlogsAdminPortlet_orderBycol%3Dtitle%26_com_liferay_blogs_web_portlet_BlogsAdminPortlet_orderByType%3Dasc%26_com_liferay_blogs_web_portlet_BlogsAdminPortlet_entriesNavigation%3D%26_com_liferay_blogs_web_portlet_BlogsAdminPortlet_cur%3D1%26_com_liferay_blogs_web_portlet_BlogsAdminPortlet_delta%3D20&_com_liferay_blogs_web_portlet_BlogsAdminPortlet_entryId=30836
\end{verbatim}
The other was just, well, friendly. She was less concerned about looking
smart and more concerned about those she interacted with, so she shared
only the important things about her. She didn't need to look fancy and
complicated. She aspired to be simple and kind to all the users and SEO
services she encountered.
\begin{verbatim}
http://localhost:8080/web/guest/home/-/blogs/lunar-scavenger-hunt
\end{verbatim}
If you want your application to be friendly to your users and to SEO
services, make your URLs friendlier. It only takes a couple steps, after
all.
\chapter{Friendly URLs}\label{friendly-urls-1}
Follow these steps to create friendly URLs:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create friendly URL routes. Create a \texttt{routes.xml} file in your
application's web module. Liferay's pattern puts it in a
\texttt{src/main/resources/META-INF/friendly-url-routes/} folder.
\item
Add friendly URL routes, using as many
\texttt{\textless{}route\textgreater{}} tags as you need friendly
URLs, like this:
\begin{verbatim}
/blogs/view
0
normal
/maximized
/blogs/view
0
maximized
/{entryId:\d+}
/blogs/view_entry
0
normal
...
\end{verbatim}
Use \texttt{\textless{}pattern\textgreater{}} tags to define
placeholder values for the parameters that normally appear in the
generated URL. This is just a mask. The beastly URL\\
still lurks beneath it.
The \texttt{pattern} value \texttt{/\{entryId:\textbackslash{}d+\}}
matches a \texttt{/} followed by an \texttt{entryId} variable that
matches the
\href{https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html}{Java
regular expression} \texttt{\textbackslash{}d+}---one or more numeric
digits. For example, a URL \texttt{/entryId}, where the
\texttt{entryId} value is \texttt{123} results in a URL value
\texttt{/123}, which matches the pattern.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Warning:** Make sure your `pattern` values don't end in a slash `/`. A
trailing slash character prevents the request from identifying the correct
route.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
**Important:** If your portlet is instanceable, you must use a variant of
the `instanceId` in the `pattern` value. If the starting value is
`render-it`, for example, use one of these patterns:
```xml
/{userIdAndInstanceId}/render-it
```
or
```xml
/{instanceId}/render-it
```
or
```xml
/{p_p_id}/render-it
```
Use `` tags to define parameters that are always the
same for the URL. For example, for a render URL, you can be certain that the
`p_p_lifecycle` parameter is always `0`. You don't have to define these
types of implicit parameters, but it's a best practice because if you don't,
they still appear in your URL.
The implicit parameters with the name `mvcRenderCommandName` are very
important. If you're using an `MVCPortlet` with `MVCRenderCommand` classes,
that parameter comes from the `mvc.command.name` property in the
`@Component` of your `MVCRenderCommand` implementation. This determines the
page that's rendered (for example, `view.jsp`).
```java
@Component(
immediate = true,
property = {
"javax.portlet.name=" + BlogsPortletKeys.BLOGS, "mvc.command.name=/",
"mvc.command.name=/blogs/view"
},
service = MVCRenderCommand.class
)
```
The [DTD file](https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-friendly-url-routes_7_2_0.dtd.html)
completely defines the `routes.xml` file.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Provide an implementation of the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/FriendlyURLMapper.html}{\texttt{FriendlyURLMapper}
service}. Create a component that specifies a
\texttt{FriendlyURLMapper} service, with two properties:
\begin{itemize}
\item
A \texttt{com.liferay.portlet.friendly-url-routes} property sets the
path to your \texttt{routes.xml} file.
\item
A \texttt{javax.portlet.name} property, which you probably have
already, specifies your portlet's name.
\end{itemize}
\begin{verbatim}
@Component(
property = {
"com.liferay.portlet.friendly-url-routes=META-INF/friendly-url-routes/routes.xml",
"javax.portlet.name=" + BlogsPortletKeys.BLOGS
},
service = FriendlyURLMapper.class
)
\end{verbatim}
\item
Implement the \texttt{FriendlyURLMapper} service. For your
convenience, the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/DefaultFriendlyURLMapper.html}{\texttt{DefaultFriendlyURLMapper}
class} provides a default implementation. If you extend
\texttt{DefaultFriendlyURLMapper} you must only override one method,
\texttt{getMapping()}. Return a String that defines the first part of
your Friendly URLs. It's smart to name it after your application.
Here's what it looks like for Liferay's Blogs application:
\begin{verbatim}
public class BlogsFriendlyURLMapper extends DefaultFriendlyURLMapper {
@Override
public String getMapping() {
return _MAPPING;
}
private static final String _MAPPING = "blogs";
}
\end{verbatim}
\end{enumerate}
All friendly URLs in Blogs begin with the String set here
(\texttt{blogs}). Let's look at one of these Friendly URLs in action.
Add a blog entry and then click on the entry's title. Look at the URL:
\begin{verbatim}
http://localhost:8080/web/guest/home/-/blogs/lunar-scavenger-hunt
\end{verbatim}
As specified in the friendly URL mapper class, \texttt{blogs} is the
first part of the friendly URL that comes after the Liferay part of the
URL. The next part is determined by a specific URL route in
\texttt{routes.xml}:
\begin{verbatim}
/{urlTitle}
/blogs/view_entry
0
normal
\end{verbatim}
The \texttt{urlTitle} is generated from the blog post's title. Since
it's already a parameter in the URL (see below), it's available for use
in the friendly URL.
\begin{verbatim}
\end{verbatim}
When the render URL is invoked, the String defined in the friendly URL
mapper teams up with the \texttt{pattern} tag in your friendly URL
routes file, and you get a very friendly URL indeed, instead of some
nasty, conceited, unfriendly URL that's despised by users and SEO
services alike.
Great! Now you know how to make your URLS friendly.
\section{Related Topics}\label{related-topics-48}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/dependency-injection}{Dependency
Injection}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset
Framework}
\end{itemize}
\chapter{Front-End Development}\label{front-end-development}
You have complete front-end development freedom. You can use Liferay
DXP's front-end frameworks, along with the front-end technologies you
love the most:
\begin{itemize}
\tightlist
\item
EcmaScript ES2015+
\item
React, Angular, Vue, etc.
\item
\href{https://metaljs.com/}{Metal.js} (developed by Liferay)
\item
\href{https://alloyui.com/}{AlloyUI} (developed by Liferay)
\item
jQuery (included)
\item
Lodash (included,
\href{https://github.com/liferay/liferay-portal/blob/master/readme/BREAKING_CHANGES.markdown\#lodash-is-no-longer-included-by-default}{but
disabled by default})
\end{itemize}
To load modules, you must know when they are needed, where they are at
build time, whether they should be bundled together or loaded
independently, and you must assemble them at runtime. Liferay's Loaders
(YUI/AUI, AMD, and npm in AMD format) handle loading for you. All you
must do is provide a small bit of information about your module.
The Liferay JS Bundle Toolkit(the
\href{https://web.liferay.com/marketplace/-/mp/application/115542926}{JS
Portlet Extender},
\href{https://www.npmjs.com/package/generator-liferay-bundle}{Liferay
Bundle Generator}, and
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-npm-bundler}{\texttt{liferay-npm-bundler}}
) has the tools you need to create and develop JavaScript portlets with
pure JavaScript tooling. You can use the
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-npm-bundler}{\texttt{liferay-npm-bundler}}
to bundle npm packages in your applications. It even has several presets
for common module types (AMD, React, Angular JS, etc.) to save you time.
It creates an OSGi bundle for you, extracts all npm dependencies, and
transpiles your code for the Liferay AMD Loader.
While developing JavaScript applications, you may need to access Liferay
DXP-specific information or web services. The \texttt{Liferay} global
JavaScript Object
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-javascript-apis}{exposes
this information for you}, to use in your JavaScript applications.
\section{Lexicon and Clay}\label{lexicon-and-clay}
Liferay uses its own design language, called
\href{https://liferay.design/lexicon}{Lexicon}, to provide a common
framework for building consistent UIs and user experiences across the
Liferay product ecosystem. The web implementation of Lexicon (CSS, JS,
and HTML) is called \href{https://clayui.com/}{Clay}. It is
automatically available to application developers through a set of CSS
classes or our
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-clay-taglib-in-your-portlets}{tag
library}.
\section{Templates}\label{templates}
For templating, you can use Java EE's JSP, FreeMarker, or whatever else
you like.
\section{Themes}\label{themes}
Themes use the standard components (CSS, JavaScript, and HTML) along
with FreeMarker templates. Although the default themes are nice, you may
wish to create your own look and feel for your site. The
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{Liferay
JS Theme Toolkit} has all the tools you need to create and develop
themes, but you can use the tools you prefer.
From the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-gradle-plugin}{Theme
Builder Gradle Plugin}, to the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}, to
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}'s
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{Theme
Template}, you can choose the development tools you like best, so you
can focus on creating a well designed theme.
\section{Front-End Extensions}\label{front-end-extensions}
Liferay DXP's modularity has many benefits for the front-end developer,
in the form of development customizations and extension points. These
extensions assure the stability, conformity, and future evolution of
your applications.
Below are some of the available front-end extensions:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site}{Theme
Contributors}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/injecting-additional-context-variables-and-functionality-into-your-theme-templates}{Context
Contributors}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/dynamic-includes}{Dynamic
Includes}
\end{itemize}
See
\href{/docs/7-2/customization/-/knowledge_base/c/theme-components}{Theme
Components} and
\href{/docs/7-2/customization/-/knowledge_base/c/understanding-the-page-structure}{Understanding
the Page Structure} for more information.
\chapter{Themes}\label{themes-1}
Themes customize the default look and feel of your site. You can inject
your own flavor and personality and represent the visual identity of
your brand or company.
You'll learn these things:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-themes}{Developing
Themes}: Learn how to use Liferay DXP's tools and features to develop
your theme.
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/extending-themes}{Extending
Themes}: Learn how to use Liferay DXP's theme extension mechanisms and
features to add to your theme.
\end{itemize}
Themes use the standard components (CSS, JS, and HTML) along with
FreeMarker templates for rendering. There are several
\href{/docs/7-2/customization/-/knowledge_base/c/theme-components\#theme-templates}{default
FreeMarker templates} that each handle a key piece of functionality for
the page. There are also
\href{/docs/7-2/customization/-/knowledge_base/c/theme-components\#theme-template-utilities}{theme
template utilities} that let you use portlets, taglibs, theme objects,
and more in your theme templates.
\href{/docs/7-2/customization/-/knowledge_base/c/theme-components\#css-frameworks-and-extensions}{CSS
extensions and patterns} come out-of-the-box, and support SASS, and
multiple JavaScript frameworks. Several mechanisms are available for
customizing, developing, and extending themes.
\chapter{Theme Workflow}\label{theme-workflow}
Themes are built on top of one of the following base themes:
\begin{itemize}
\tightlist
\item
\textbf{Unstyled:} provides basic markup, functions, and images
\item
\textbf{Styled:} inherits from the Unstyled base theme and adds some
styling on top
\end{itemize}
Themes can be built with your choice of tooling. Liferay also offers its
own set of tools to get your themes up and running quickly.
The following Liferay tools help you build themes:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-gradle-plugin}{Theme
Builder Gradle Plugin}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}'s
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{Theme
Template}.
\end{itemize}
Depending on the tool you choose (
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Theme
Generator},
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-gradle-plugin}{Gradle},
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{Blade CLI},
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{Maven}, or
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{Dev Studio}
), the theme anatomy can be different. The overall development process
is the same:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Mirror the structure of the files you want to modify. Most of the
time, you'll modify these files:
\begin{itemize}
\tightlist
\item
\texttt{portal\_normal.ftl}: main theme markup
\item
\texttt{\_custom.scss}: custom CSS styling
\item
\texttt{main.js}: the theme's JavaScript
\end{itemize}
\item
Build and deploy the theme.
\item
Apply the theme
\href{/docs/7-2/user/-/knowledge_base/u/page-set-look-and-feel}{through
the Look and Feel menu} by selecting your
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-thumbnail-preview-for-your-theme}{theme's
thumbnail}.
\end{enumerate}
The finished theme is bundled as a WAR (Web application ARchive) file.
\noindent\hrulefill
\textbf{Note:} While developing your theme, you should enable
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Developer
Mode}. This disables the JavaScript minifier and caching for CSS and
FreeMarker template files, which makes debugging easier.
\noindent\hrulefill
If you've built your theme with the Liferay Theme Generator, you can use
some helpful Gulp tasks to streamline the process:
\begin{itemize}
\tightlist
\item
\textbf{build:} builds your theme's files based on the specified base
theme. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/building-your-themes-files}{gulp
build tutorial} for more information.
\item
\textbf{extend:} sets the base theme or themelet to extend. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/changing-your-base-theme}{gulp
extend tutorial} for more information.
\item
\textbf{init:} specifies the app server to deploy your theme to
(automatically run during the initial creation of the theme). See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-your-themes-app-server}{gulp
init tutorial} for more information.
\item
\textbf{kickstart:} copies files from an existing theme into your
theme to help kickstart it. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-an-existing-themes-files}{gulp
kickstart tutorial} for more information.
\item
\textbf{status:} lists the base theme/themelets that your theme
extends. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/listing-your-themes-extensions}{gulp
status tutorial} for more information.
\item
\textbf{watch:} watches for changes to your theme's files and
automatically deploys them to the server when a change is made. See
the
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-deploying-theme-changes}{gulp
watch tutorial} for more information.
\end{itemize}
See
\href{/docs/7-2/customization/-/knowledge_base/c/theme-components}{Theme
Components} and
\href{/docs/7-2/customization/-/knowledge_base/c/understanding-the-page-structure}{Understanding
the Page Structure} to get a top-level overview of how themes work.
\chapter{Developing Themes}\label{developing-themes}
Theme projects created using the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Liferay
Theme Generator} have access to several
\href{https://www.npmjs.com/package/gulp}{gulp} tasks you can execute to
manage and develop your theme. This section covers the available actions
that these tasks provide, as well as other information you may find
useful while developing your theme.
This section covers these topics:
\begin{itemize}
\tightlist
\item
Using liferay theme tasks (build, deploy, extend, init, kickstart,
status, and watch)
\item
Using Developer Mode
\item
Creating thumbnail previews for your theme
\item
Creating color schemes for your theme
\item
Making configurable theme settings
\end{itemize}
While developing your theme, you may notice that your theme's CSS and JS
files are minified. This optimizes performance, but it can make
debugging difficult during development. Developer Mode, disabled by
default, optimizes development instead. For instance, it loads CSS and
JS files individually for easier debugging. Also, you don't have to
reboot the server as often in Developer Mode. Here is a list of
Developer Mode's key behavior changes and the
\href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html}{Portal
Property} override settings that trigger them (if applicable):
\begin{itemize}
\tightlist
\item
CSS files are loaded individually rather than being combined and
loaded as a single CSS file (\texttt{theme.css.fast.load=false}).
\item
Layout template caching is disabled
(\texttt{layout.template.cache.enabled=false}).
\item
The server does not launch a browser when starting
(\texttt{browser.launcher.url=}).
\item
FreeMarker Templates for themes and web content are not cached, so
changes are applied immediately (via the system setting in your
Liferay DXP instance).
\item
Minification of CSS and JavaScript resources is disabled
(\texttt{minifier.enabled=false}).
\end{itemize}
Individual file loading of your styling and behaviors, combined with
disabled caching for layout and FreeMarker templates, lets you see your
changes more quickly. These developer settings are defined in the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-impl/src/portal-developer.properties}{\texttt{portal-developer.properties}
file}. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Using
Developer Mode with Themes} to learn how to enable Developer Mode in
your app server. You can also use the
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-deploying-theme-changes}{Gulp
Watch task} to test theme changes on a proxy port before deploying your
theme to your server.
\chapter{Using Developer Mode with
Themes}\label{using-developer-mode-with-themes}
This article shows how to enable Developer Mode in your app server
manually and through Dev Studio DXP, as well as how to configure other
settings that may benefit you during development. Each topic is
explained in the relevant section below.
\section{Enabling Developer Mode
Manually}\label{enabling-developer-mode-manually}
Follow these steps to enabled Developer Mode in your app server
manually:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a \texttt{portal-ext.properties} file in your server's root
folder if it doesn't exist.
\item
Add the line below to it:
\begin{verbatim}
include-and-override=portal-developer.properties
\end{verbatim}
Alternatively, add the properties from the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-impl/src/portal-developer.properties}{portal-developer.properties}
file to your \texttt{portal-ext.properties} file that you want to use.
\item
Start your app server to apply the changes.
\end{enumerate}
Read the next section to learn how to enable Developer Mode in Dev
Studio DXP.
\section{Setting Developer Mode in Dev Studio
DXP}\label{setting-developer-mode-in-dev-studio-dxp}
Follow these steps to enable Developer Mode for your app server in Dev
Studio DXP:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Double-click on your server in the \emph{Servers} window and open the
\emph{Liferay Launch} section.
\item
Select \emph{Custom Launch Settings} and check the \emph{Use developer
mode} option.
\item
Save the changes and start your server.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/developer-mode-ide.png}
\caption{The \emph{Use developer mode} option lets you enable Developer
Mode for your server in Dev Studio DXP.}
\end{figure}
\noindent\hrulefill
\textbf{Warning:} Only change the Server settings from the runtime
environment's Liferay Launch section.
\noindent\hrulefill
\section{Configuring FreeMarker System
Settings}\label{configuring-freemarker-system-settings}
By default, FreeMarker theme templates and web content templates are
cached. You can change this behavior through System Settings with the
steps below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the Control Panel and go to \emph{Configuration} → \emph{System
Settings}.
\item
Select \emph{Template Engines} under the \emph{PLATFORM} heading.
\item
By default, the \emph{Resource modification check} (the time in
milliseconds that the template is cached) is set to \texttt{60000}.
Set this value to \texttt{0} to disable caching.
\end{enumerate}
Your FreeMarker templates are ready for development. Next you can learn
how to improve JavaScript file loading for development.
\section{JavaScript Fast Loading}\label{javascript-fast-loading}
By default, JavaScript fast loading is enabled in Developer Mode
(\texttt{javascript.fast.load=true}). This loads the packed version of
files listed in the
\href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#JavaScript}{Portal
Properties} \texttt{javascript.barebone.files} or
\texttt{javascript.everything.files}. You can, however, disable
JavaScript fast loading for easier debugging for development. Just set
\texttt{javascript.fast.load} to \texttt{false} in your
\texttt{portal.properties}, or you can disable fast loading by setting
the URL parameter \texttt{js\_fast\_load} to \texttt{0}.
\noindent\hrulefill
\textbf{Note:} JavaScript fast loading is retrieved from one of three
places: the request (determined by the current URL:
\texttt{http://localhost:8080/web/guest/home?js\_fast\_load=1}(on) or
\texttt{...?js\_fast\_load=0}(off)), the Session, or the Portal Property
(\texttt{javascript.fast.load=true}). Preference is given in the order
of request, session, and then Portal Properties. This lets you change
\texttt{js\_fast\_load}'s value from the default in
\texttt{portal.properties} without having to manually re-enter
\texttt{js\_fast\_load} into the URL upon every new page load.
\noindent\hrulefill
Great! You've set up your server for Developer Mode. Now, when you
modify your theme's file directly in your bundle, you can see your
changes applied immediately on redeploying your theme!
\section{Related Topics}\label{related-topics-49}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Generating
Layout Templates with the Theme Generator}
\end{itemize}
\chapter{Building Your Theme's Files}\label{building-your-themes-files}
Follow these steps to build your theme's files with the Build task. Note
that this task only works for themes that use the
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{liferay
JS Theme Toolkit}, such as those created with the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your theme's root folder and run \texttt{gulp\ build}.
\item
The \texttt{gulp\ build} task generates the base theme files (in the
\texttt{build} folder), compiles Sass into CSS, and compresses all
theme files into a \texttt{.war} file (in the \texttt{dist} folder),
that you can deploy to your server. Copy any of these files and
folders to your theme's \texttt{src} folder to modify them.
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{Deploy}
the \texttt{war} file to your app server to make it available.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/theme-dev-building-themes-gulp-build.png}
\caption{Run the \texttt{gulp\ build} task to build your theme's files.}
\end{figure}
\section{Related Topics}\label{related-topics-50}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-deploying-theme-changes}{Automatically
Deploying Theme Changes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-an-existing-themes-files}{Copying
an Existing Theme's Files}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{Deploying
and Applying Themes}
\end{itemize}
\chapter{Deploying and Applying
Themes}\label{deploying-and-applying-themes}
Follow these steps to deploy your theme with the Deploy task. Note that
this task only works for themes that use the
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{liferay
JS Theme Toolkit}, such as those created with the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
Follow these steps to deploy your theme:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Navigate to your theme's root folder and run \texttt{gulp\ deploy}.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** If you're running the
[Felix Gogo shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell),
you can also deploy your theme using the `gulp deploy:gogo` command.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Your server's log displays that the OSGi bundle is started.
\begin{figure}
\centering
\includegraphics{./images/theme-dev-deploying-themes-server-log.png}
\caption{Your server's log notifies you when the theme's bundle has
started.}
\end{figure}
\item
Apply your theme through the \emph{Build} → \emph{Pages} menu in the
Control Menu. Select the \emph{Configure} option for your site pages,
and click the \emph{Change Current Theme} button to apply your theme.
\end{enumerate}
Wonderful! Your theme is deployed to your server and applied to your
site.
\section{Related Topics}\label{related-topics-51}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-deploying-theme-changes}{Automatically
Deploying Theme Changes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-an-existing-themes-files}{Copying
an Existing Theme's Files}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Creating
Themelets with the Theme Generator}
\end{itemize}
\chapter{Updating Your Theme's App
Server}\label{updating-your-themes-app-server}
Follow these steps to update the configuration for your theme's app
server with the Init task. Note that this task only works for themes
that use the
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{liferay
JS Theme Toolkit}, such as those created with the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your theme's root folder and run \texttt{gulp\ init}.
\begin{figure}
\centering
\includegraphics{./images/theme-dev-server-configuration-gulp-init.png}
\caption{Run the \texttt{gulp\ init} task to update your app server
configuration.}
\end{figure}
\item
Enter the updated path to your app server and site.
\item
Your theme's \texttt{liferay-theme.json} file contains the updated
server configuration information:
\begin{verbatim}
{
"LiferayTheme": {
"deploymentStrategy": "LocalAppServer",
"appServerPath": "C:\\Users\\liferay\\opt\\Liferay\\bundles\\liferay-ce-portal-tomcat-7.2.0\\liferay-ce-portal-7.2.0\\tomcat-9.0.10",
"deployPath": "C:\\Users\\liferay\\opt\\Liferay\\bundles\\liferay-ce-portal-tomcat-7.2.0\\liferay-ce-portal-7.2.0\\deploy",
"url": "http://localhost:8080",
"appServerPathPlugin": "C:\\Users\\liferay\\opt\\Liferay\\bundles\\liferay-ce-portal-tomcat-7.2.0\\liferay-ce-portal-7.2.0\\tomcat-9.0.10\\webapps\\my-new72theme-theme",
"deployed": true,
"pluginName": "my-new72theme-theme"
}
}
\end{verbatim}
\end{enumerate}
Awesome! Now you can
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{deploy
your theme} to the proper server.
\section{Related Topics}\label{related-topics-52}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-deploying-theme-changes}{Automatically
Deploying Theme Changes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/changing-your-base-theme}{Changing
Your Base Theme}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/listing-your-themes-extensions}{Listing
Your Theme's Extensions}
\end{itemize}
\chapter{Automatically Deploying Theme
Changes}\label{automatically-deploying-theme-changes}
Follow these steps to automatically preview your theme's changes with
the Watch task. Note that this task only works for themes that use the
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{liferay
JS Theme Toolkit}, such as those created with the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Navigate to your theme's root folder and run \texttt{gulp\ watch}.
This sets up a proxy for your app server and opens it in a new window
in the browser.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Live changes are only viewable on port `9080`
(`http://localhost:9080`). Live changes **are not viewable** on your app
server (e.g. `http://localhost:8080`).
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}

\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Make a change to your theme and save the file. The updated files are
built, compiled, and copied directly to the proxy port
(e.g.~\texttt{9080}). CSS changes are deployed live, so no page reload
is needed.
\item
Once you're happy with the changes,
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{re-deploy}
your theme to apply the changes to your site on your app server.
\begin{figure}
\centering
\includegraphics{./images/theme-dev-watching-themes-gulp-watch-auto-deploy.png}
\caption{The watch task notifies you that the changes are deployed.}
\end{figure}
\end{enumerate}
\section{Related Topics}\label{related-topics-53}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-your-themes-app-server}{Configuring
Your Theme's App Server}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/copying-an-existing-themes-files}{Copying
an Existing Theme's Files}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{Deploying
and Applying Themes}
\end{itemize}
\chapter{Creating a Thumbnail Preview for Your
Theme}\label{creating-a-thumbnail-preview-for-your-theme}
When you apply a theme to your site pages, you have to choose from the
list of available themes in the site selector. The only identification
for each theme is the theme's name, along with a small thumbnail preview
image that gives a brief impression of the theme. This is even more
important when developing color schemes for a theme, since names are not
displayed for color schemes.
This article shows how to create a thumbnail preview for your theme so
users can identify it.
\begin{figure}
\centering
\includegraphics{./images/theme-dev-theme-thumbnail-default.png}
\caption{Your theme thumbnail is displayed with the rest of the
available themes.}
\end{figure}
Your first step in creating a thumbnail preview for your theme is taking
a screenshot of your theme. Once you have a screenshot that you like,
follow the steps below to create a thumbnail preview for your theme:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Resize the screenshot to 270 pixels high by 480 pixels wide. Your
thumbnail \emph{must be} these exact dimensions to display properly.
\item
Save the image as a \texttt{.png} file named \texttt{thumbnail.png}
and place it in the theme's \texttt{src/images} folder (create this
folder if it doesn't already exist).
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** The
[Theme Builder Gradle plugin](/docs/7-2/reference/-/knowledge_base/r/theme-builder-gradle-plugin)
doesn't recognize a `thumbnail.png` file. If you're using this plugin to
build your theme instead, you must create a `screenshot.png` file in your
theme's `images` folder that is 1080 pixels high by 864 pixels wide. The
thumbnail is automatically generated from the screenshot for you when the
theme is built.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
Redeploy the theme. The file is displayed as the theme's thumbnail.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/theme-dev-theme-thumbnail-custom.png}
\caption{Your theme thumbnail is displayed with the rest of the
available themes.}
\end{figure}
\section{Related Topics}\label{related-topics-54}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Installing
the Theme Generator and Creating a Theme}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Creating
Themelets with the Theme Generator}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-color-schemes-for-your-theme}{Creating
Color Schemes for Your Theme}
\end{itemize}
\chapter{Creating Color Schemes for Your
Theme}\label{creating-color-schemes-for-your-theme}
Color schemes give your theme additional color palettes. With just a
small amount of changes to your theme's CSS, you can subtly change the
look of your theme, while maintaining the same design and feel to it.
\begin{figure}
\centering
\includegraphics{./images/theme-dev-color-schemes.png}
\caption{Color schemes give administrators some choices for your theme's
look.}
\end{figure}
Follow these steps to create color schemes for your theme:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the theme's \texttt{WEB-INF/liferay-look-and-feel.xml} file and
follow the pattern below to add the default color scheme. If your
default styles are in \texttt{\_custom.scss}, use the \texttt{default}
\texttt{\textless{}css-class\textgreater{}} as shown in the example
below. See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-look-and-feel_7_2_0.dtd.html\#color-scheme}{liferay-look-and-feel
DTD} for an explanation of each of the elements used below:
\begin{verbatim}
true
default
${images-path}/my_color_schemes_folder_name/${css-class}
...
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Color schemes are sorted alphabetically by `name` rather than
`id`. For example, a color scheme named `Clouds` and `id` `02` would be
selected by default over a color scheme named `Day` with `id` `01`. The
`` element overrides the alphabetical sorting and sets the
color scheme that is selected by default when the theme is chosen.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Add the remaining color schemes below the default color scheme, using
the pattern below. Note that the IDs, names, and CSS classes must be
unique for each color scheme.
\begin{verbatim}
color-scheme-css-class
\end{verbatim}
An example \texttt{liferay-look-and-feel.xml} configuration is shown
below:
\begin{verbatim}
7.2.0+
ftl
true
default
${images-path}/color_schemes/${css-class}
dark
light
...
\end{verbatim}
\item
Create a folder for your color schemes (\texttt{color\_schemes} for
example) in the theme's \texttt{css} folder, and add a \texttt{.scss}
file to it for each color scheme your theme supports, excluding the
default color scheme since those styles are included in
\texttt{\_custom.scss}.
\item
The color scheme class is added to the theme's
\texttt{\textless{}body\textgreater{}} element when the color scheme
is applied, so add the class to the color scheme's styles to target
the proper color scheme. The example below specifies styles for a
color scheme with the class \texttt{day}:
\begin{verbatim}
body.day { background-color: #DDF; }
.day a { color: #66A; }
\end{verbatim}
\item
Import the color scheme \texttt{.scss} files into the theme's
\texttt{\_custom.scss} file. The example below imports
\texttt{\_day.scss} and \texttt{\_night.scss} files:
\begin{verbatim}
@import "color_schemes/day";
@import "color_schemes/night";
\end{verbatim}
\item
Create a folder for each color scheme in your theme's \texttt{images}
folder, and add
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-thumbnail-preview-for-your-theme}{a
thumbnail preview} for them. The folder name \emph{must match} the
color scheme's CSS class name.
\end{enumerate}
There you have it. Now you can go color scheme crazy with your themes!
\section{Related Topics}\label{related-topics-55}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Generating
Layout Templates}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-thumbnail-preview-for-your-theme}{Creating
a Thumbnail Preview for Your Theme}
\end{itemize}
\chapter{Making Configurable Theme
Settings}\label{making-configurable-theme-settings}
If you have an aspect of a theme that you want an Administrator to
configure without having to manually update and redeploy the theme, you
can create a \emph{theme setting} for it. Theme settings are very
versatile and can be customized to meet your needs.
\begin{figure}
\centering
\includegraphics{./images/theme-dev-configurable-theme-settings.png}
\caption{Here are examples of configurable settings for the site Admin.}
\end{figure}
Follow the steps below to create theme settings:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your theme's \texttt{WEB-INF/liferay-look-and-feel.xml} file, and
follow the pattern below to nest a
\texttt{\textless{}setting/\textgreater{}} element inside the parent
\texttt{\textless{}settings\textgreater{}} element for each setting
you want to add:
\begin{verbatim}
7.2.0+
ftl
portlet decorators...
\end{verbatim}
The example below adds a text input setting for a custom hex code:
\begin{verbatim}
\end{verbatim}
See the \texttt{liferay-look-and-feel.xml}'s
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-look-and-feel_7_2_0.dtd.html\#settings}{DTD
docs} for an explanation of the setting's configuration options.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can modify theme settings with JavaScript to provide a more
custom experience. The example below modifies the theme setting, changing
its `type` to `color`, to provide a color picker for the user:
```xml
```
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
Create a file called \texttt{init\_custom.ftl} in your theme's
\texttt{templates} folder if it doesn't already exist, and follow the
patterns in the table below to define your theme setting variables in
it:
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
Return Type | Description | Pattern |
--- | --- | --- |
Boolean | a select box with the options `true` and `false` or a checkbox with values `yes` and `no` | `<#assign my_variable_name = getterUtil.getBoolean(themeDisplay.getThemeSetting("theme-setting-key"))/>` |
String | a text input or text area input | `<#assign my_variable_name = getterUtil.getString(themeDisplay.getThemeSetting("theme-setting-key"))/>` |
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
The example below adds a custom hex code setting:
<#assign my_hex_code =
getterUtil.getString(themeDisplay.getThemeSetting("my-hex-code"))/>
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Add your theme setting variables to the theme template. The example
below prints \texttt{my\_hex-code}'s value as the value of the
header's \texttt{style} attribute:
\texttt{portal\_normal.ftl}:
\begin{verbatim}
\end{verbatim}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{Deploy
the theme} to apply the changes. To set the theme setting for a Public
or Private page set, click the \emph{Gear icon} next to the page set
you want to configure and update the setting under the \emph{Look and
Feel} tab. Alternatively, you can set the theme setting for an
individual page by opening the \emph{Actions menu} next to the page
and selecting \emph{Configure} and choosing the \emph{Define a
Specific look and feel for this page} option.
\end{enumerate}
Great! You've created configurable settings for your theme.
\section{Related Topics}\label{related-topics-56}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Creating
Themelets with the Theme Generator}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/listing-your-themes-extensions}{Listing
Your Theme's Extensions}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/importing-resources-with-a-theme}{Importing
Resources with a Theme}
\end{itemize}
\chapter{Using Font Awesome and Glyph Icons in Your
Theme}\label{using-font-awesome-and-glyph-icons-in-your-theme}
By default, \href{https://fontawesome.com/v3.2.1/}{Font Awesome v3.2.1}
and \href{https://getbootstrap.com/docs/3.3/components/}{Bootstrap 3
Glyphicons} are enabled globally in Liferay DXP via a system setting.
This means that you can use them in your themes to create social media
links, for example. A Site Administrator can disable this to improve
performance, if they choose.
\section{Disabling Enabling Global Font Awesome and Glyphicons in
Portal}\label{disabling-enabling-global-font-awesome-and-glyphicons-in-portal}
Since Liferay DXP Fix Pack 2 and Liferay Portal 7.2 CE GA2, Font Awesome
is available globally as a system setting, which is enabled by default.
You can disable this setting to improve performance. To update the
setting, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the Control Menu and navigate to \emph{Control Panel} →
\emph{Configuration} → \emph{System Settings} and select \emph{Third
Party} under the \emph{PLATFORM} heading.
\item
Select \emph{Font Awesome} under the \emph{System Scope} and
check/uncheck the \emph{Enable Font Awesome} checkbox to
enable/disable Font Awesome icons and Glyphicons across the site.
\item
Click \emph{Save} to save the configuration.
\end{enumerate}
\section{Including Font Awesome and Glyphicons in Your
Theme}\label{including-font-awesome-and-glyphicons-in-your-theme}
As a safeguard, you should include Font Awesome and Glyphicons with your
theme if you want to use them. This ensures that your icons won't break
if the global system setting is disabled. If you created the theme with
the Liferay Theme Generator and answered yes (y) to the Font Awesome
prompt, the Font Awesome dependency is added which includes Font Awesome
and Glyphicons for you. If you didn't include Font Awesome and
Glyphicons when you initially created the theme, follow these steps to
include them in your theme now:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use the Font Awesome v3.2.1 or Bootstrap 3 Glyphicons in your theme's
template. The example below uses Font Awesome icons:
\begin{verbatim}
\end{verbatim}
\item
Open the theme's \texttt{package.json} and include the
\texttt{liferay-font-awesome} dev dependency:
\begin{verbatim}
"liferay-font-awesome": "3.4.0"
\end{verbatim}
\item
Run \texttt{gulp\ deploy} from the theme's root folder to build and
deploy the theme's files. This adds a \texttt{/css/font/} folder to
the theme's \texttt{build} folder that contains the Font Awesome and
Glyphicon fonts.
\end{enumerate}
\section{Related Topics}\label{related-topics-57}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Installing
the Theme Generator and Creating a Theme}
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-theme-to-7-2}{Upgrading
Themes}
\end{itemize}
\chapter{Extending Themes}\label{extending-themes}
Liferay DXP has additional features that you can use to extend your
theme and modify your site. This section covers these topics:
\begin{itemize}
\tightlist
\item
Installing Themelets
\item
Injecting additional context variables into your theme templates
\item
Packaging independent UI resources for your site
\item
Copying an existing theme's files
\item
Listing your theme's extensions
\item
Overwriting and extending liferay theme tasks
\end{itemize}
\chapter{Installing a Themelet in Your
Theme}\label{installing-a-themelet-in-your-theme}
After you've created your
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{themelet},
follow the steps below to install it into your theme.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your theme's root folder and run \texttt{gulp\ extend}.
\item
Choose \emph{Themelet} as the theme asset to extend.
\item
Select \emph{Search globally installed npm modules}, \emph{Search npm
registry}, or \emph{Specify a package URL} to locate the themelet.
\begin{figure}
\centering
\includegraphics{./images/install-themelet.png}
\caption{You can extend your theme using globally installed npm
modules or published npm modules.}
\end{figure}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can retrieve the URL for a package by running
`npm show package-name dist.tarball`.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
Highlight your themelet, press spacebar to activate it, and press
\emph{Enter} to install it.
\end{enumerate}
Great, now you know how to install a themelet in your theme! The next
time you
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{deploy}
your theme, the themelet will be bundled along with it.
\section{Related Topics}\label{related-topics-58}
-\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Generating
Themelets with the Theme Generator}
-\href{/docs/7-2/frameworks/-/knowledge_base/f/injecting-additional-context-variables-and-functionality-into-your-theme-templates}{Injecting
Additional Context Variables and Functionality into Your Theme
Templates}
-\href{/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site}{Packaging
Independent UI Resources for Your Site}
\chapter{Injecting Additional Context Variables and Functionality into
Your Theme
Templates}\label{injecting-additional-context-variables-and-functionality-into-your-theme-templates}
JSPs are native to Java EE and therefore have access to all the
contextual objects inherit to the platform, like the request and
session. Through these objects, developers can obtain Liferay
DXP-specific context information by accessing container objects like
\texttt{themeDisplay} or \texttt{serviceContext}. This, however, is not
the case for FreeMarker templates. To access this information in
FreeMarker templates, you must inject it yourself into the template's
context. Liferay DXP gives you a head start by injecting several common
objects into the template's context and exposing them as
\href{/docs/7-2/reference/-/knowledge_base/r/product-freemarker-macros}{FreeMarker
macros}. To inject other objects into the FreeMarker template's context,
you must create a \emph{Context Contributor}.
You can create a Context Contributor to use non-JSP templating languages
for themes, widget templates, and any other templates used in Liferay
DXP. For example, suppose you want your theme to change color based on
the user's organization. You could create a Context Contributor to
inject the user's organization into your theme's context, and then
determine the theme's color based on that information.
Follow the steps below to create a context contributor:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an OSGi module using your favorite third party tool, or use
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}.
\item
Create a component class that implements the
\texttt{TemplateContextContributor} service, and set the \texttt{type}
property to the type of context you're injecting into. Set it to
\texttt{TYPE\_THEME} to inject context-specific variables for your
theme, or set it to \texttt{TYPE\_GLOBAL} to inject it into every
context execution in Liferay DXP, like themes, widget templates, DDM
templates, etc, as defined in
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/template/TemplateContextContributor.html}{TemplateContextContributor}.
To follow naming conventions, begin the class name with the entity you
want to inject context-specific variables for, followed by
\emph{TemplateContextContributor} (e.g.,
\texttt{ProductMenuTemplateContextContributor}):
\begin{verbatim}
@Component(
immediate = true,
property = {"type=" + TemplateContextContributor.TYPE_THEME},
service = TemplateContextContributor.class
)
\end{verbatim}
\item
Implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/template/TemplateContextContributor.html}{TemplateContextContributor}
interface in your \texttt{*TemplateContextContributor} class, and
overwrite the
\texttt{prepare(Map\textless{}String,Object\textgreater{},\ HttpServletRequest)}
method to inject new or modified variables into the
\texttt{contextObjects} map. This is your template's context that was
described earlier.
\end{enumerate}
The \texttt{ProductMenuTemplateContextContributor}'s class is shown as
an example below. It overwrites the \texttt{prepare(...)} method to
inject a modified \texttt{bodyCssClass} variable and a new
\texttt{liferay\_product\_menu\_state} variable into the theme context
for the Product Menu. Specifically, the \texttt{cssClass} variable
provides styling for the Product Menu and the \texttt{productMenuState}
variable determines whether the visible Product Menu should be open or
closed:
\begin{verbatim}
@Override
public void prepare(
Map contextObjects, HttpServletRequest request) {
if (!isShowProductMenu(request)) {
return;
}
String cssClass = GetterUtil.getString(
contextObjects.get("bodyCssClass"));
String productMenuState = SessionClicks.get(
request,
ProductNavigationProductMenuWebKeys.
PRODUCT_NAVIGATION_PRODUCT_MENU_STATE,
"closed");
contextObjects.put(
"bodyCssClass", cssClass + StringPool.SPACE + productMenuState);
contextObjects.put("liferay_product_menu_state", productMenuState);
}
\end{verbatim}
The \texttt{ProductMenuTemplateContextContributor} provides an easy way
to inject variables into Liferay DXP's theme directly related to the
Product Menu. You can do the same with your custom context contributor.
With the power to inject additional variables into any context in
Liferay DXP, you're free to fully harness the power of your chosen
templating language.
\section{Related Topics}\label{related-topics-59}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-themes}{Developing
Themes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site}{Theme
Contributors}
\end{itemize}
\chapter{Packaging Independent UI Resources for Your
Site}\label{packaging-independent-ui-resources-for-your-site}
If you want to package UI resources independent of a specific theme and
include them on every page, a \emph{Theme Contributor} is your best
option. If, instead, you want to include separate UI resources on a page
that are attached to a theme, use
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{themelets}.
A Theme Contributor is a module that contains CSS and JS resources to
apply to the page. The Control Menu, Product Menu, and Simulation Panel
are packaged as Theme Contributors.
\begin{figure}
\centering
\includegraphics{./images/theme-contributor-menus-diagram.png}
\caption{The Control Menu, Product Menu, and Simulation Panel are
packaged as Theme Contributor modules.}
\end{figure}
If you want to edit or style these standard UI components, you must
create a Theme Contributor and add your modifications on top. You can
also add new UI components to Liferay DXP by creating a Theme
Contributor. This article shows how to create a Theme Contributor
module.
Follow these steps to create a Theme Contributor:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a generic OSGi module using your favorite third party tool, or
use \href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade
CLI}. You can also use the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-contributor-template}{Blade
Template} to create your module, in which case you can skip step 2.
\item
Add the \texttt{Liferay-Theme-Contributor-Type} header to your
module's \texttt{bnd.bnd} file to identify your module as a Theme
Contributor, and add the \texttt{Web-ContextPath} header to set the
context from which the Theme Contributor's resources are hosted. See
the
\href{https://search.maven.org/search?q=a:com.liferay.product.navigation.control.menu.theme.contributor}{Control
Menu module's} \texttt{bnd.bnd} below as an example:
\begin{verbatim}
Bundle-Name: Liferay Product Navigation Product Menu Theme Contributor
Bundle-SymbolicName: com.liferay.product.navigation.product.menu.theme.contributor
Bundle-Version: 3.0.4
Liferay-Theme-Contributor-Type: product-navigation-product-menu
Web-ContextPath: /product-navigation-product-menu-theme-contributor
\end{verbatim}
The Theme Contributor type helps Liferay DXP better identify your
module. If you're creating a Theme Contributor to override an existing
Theme Contributor, you should try to use the same type to maximize
compatibility with future developments.
\item
Add the \texttt{Liferay-Theme-Contributor-Weight} to your
\texttt{bnd.bnd} file to set a priority for your Theme Contributor. To
override another Theme Contributor's styles, such as those for the
Control Menu, set a higher weight. The higher the value, the higher
the priority. If your Theme Contributor has a weight of 100, it will
be loaded after one with a weight of 99, allowing your CSS to override
theirs:
\begin{verbatim}
Liferay-Theme-Contributor-Weight: [value]
\end{verbatim}
\item
Create a \texttt{src/main/resources/META-INF/resources} folder in your
module and place your resources (CSS and JS) in that folder.
\item
Build and deploy your module to see your modifications applied to
Liferay DXP pages and themes.
\end{enumerate}
That's all you need to do to create a Theme Contributor for your site.
Remember, with great power comes great responsibility, so use Theme
Contributors wisely. The UI contributions affect every page and aren't
affected by theme deployments.
\section{Related Topics}\label{related-topics-60}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-themes}{Developing
Themes}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Generating
Themelets}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/installing-a-themelet-in-your-theme}{Installing
a Themelet}
\end{itemize}
\chapter{Changing Your Base Theme}\label{changing-your-base-theme}
Follow these steps to change your theme's base theme with the Extend
task. Note that this task only works for themes that use the
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{liferay
JS Theme Toolkit}, such as those created with the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your theme's root folder and run \texttt{gulp\ extend}.
\begin{figure}
\centering
\includegraphics{./images/theme-ext-changing-base-themes-gulp-extend-base-theme.png}
\caption{Run the \texttt{gulp\ extend} task to change your base
theme.}
\end{figure}
\item
Enter 1 to select a new base theme to extend.
\item
By default, themes created with the
\href{https://github.com/liferay/generator-liferay-theme}{Liferay
Theme Generator} are based off of the
\href{https://www.npmjs.com/package/liferay-theme-styled}{styled
theme}. You can extend the styled or unstyled base theme, a globally
installed theme, a theme published on the npm registry, or you can
specify a package URL. Enter the number for the option you wish to
select.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can retrieve the URL for a package by running
`npm show package-name dist.tarball`.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}

\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
**Note:** The Classic theme is an implementation of an existing base theme
and is therefore not meant to be extended. Extending Liferay's Classic
theme is strongly discouraged.
\end{verbatim}
\noindent\hrulefill
Your theme's \texttt{package.json} contains the updated base theme
configuration:
\begin{verbatim}
"liferayTheme": {
"baseTheme": "styled",
"screenshot": "",
"templateLanguage": "ftl",
"version": "7.2"
},
\end{verbatim}
Great! You've updated your base theme. When you
\href{/docs/7-2/frameworks/-/knowledge_base/f/building-your-themes-files}{build
your theme's files} or
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{deploy
it}, your theme will inherit the updated base theme's files.
\section{Related Topics}\label{related-topics-61}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-your-themes-app-server}{Configuring
Your Theme's App Server}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{Deploying
and Applying Themes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/listing-your-themes-extensions}{Listing
Your Theme's Extensions}
\end{itemize}
\chapter{Copying an Existing Theme's
Files}\label{copying-an-existing-themes-files}
Follow these steps to copy an existing theme's files with the Kickstart
task. Unlike extending a base theme, which is a dynamic inheritance that
applies your \texttt{src} files on top of the base theme on every build,
the Kickstart task is a one time inheritance.
\noindent\hrulefill
\textbf{Warning:} The gulp kickstart task copies an existing theme's
files into your own, which can potentially overwrite files with the same
name. Proceed with caution.
\noindent\hrulefill
Note that this task only works for themes that use the
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{liferay
JS Theme Toolkit}, such as those created with the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your theme's root folder and run \texttt{gulp\ kickstart}.
\begin{figure}
\centering
\includegraphics{./images/theme-ext-kickstarting-themes-gulp-kickstart.png}
\caption{Run the \texttt{gulp\ kickstart} task to copy a theme's files
into your own theme.}
\end{figure}
\item
Select where to search for the theme to copy. You can copy files from
globally installed themes or themes published on the npm registry.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** **You can't kickstart the Classic Theme.**
\end{verbatim}
\noindent\hrulefill
\noindent\hrulefill
\begin{verbatim}
**Note:** To globally install a theme, run the `npm link` command from the
theme's root folder.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}

\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
The theme's files are copied into your own theme, jump starting
development. Add your changes on top of these files.
\end{enumerate}
Congrats! Now you have a head start to developing your theme.
\section{Related Topics}\label{related-topics-62}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/building-your-themes-files}{Building
Your Theme's files}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Generating
Themelets with the Theme Generator}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes}{Deploying
and Applying Themes}
\end{itemize}
\chapter{Listing Your Theme's
Extensions}\label{listing-your-themes-extensions}
Do you need to know what base theme/themelet(s) your theme extends?
Follow these steps to list your theme's extensions with the Status task.
Note that this task only works for themes that use the
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{liferay
JS Theme Toolkit}, such as those created with the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator}.
\noindent\hrulefill
\textbf{Note:} Gulp is included as a local dependency in generated
themes, so you are not required to install it. It can be accessed by
running \texttt{node\_modules\textbackslash{}.bin\textbackslash{}gulp}
followed by the Gulp task from a generated theme's root folder.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your theme's root folder.
\item
Run \texttt{gulp\ status} to print your theme's current extensions to
the command line.
\end{enumerate}
Your theme's current extensions are also found under the
\texttt{baseTheme} and \texttt{themeletDependencies} headings in your
theme's \texttt{package.json}.
\begin{figure}
\centering
\includegraphics{./images/theme-ext-listing-theme-extensions.png}
\caption{Run the \texttt{gulp\ status} task to list your theme's current
extensions.}
\end{figure}
\section{Related Topics}\label{related-topics-63}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/changing-your-base-theme}{Changing
Your Base Theme}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-your-themes-app-server}{Configuring
Your Theme's App Server}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Generating
Themelets with the Theme Generator}
\end{itemize}
\chapter{Overwriting and Extending Liferay Theme
Tasks}\label{overwriting-and-extending-liferay-theme-tasks}
Themes created with the Liferay Theme Generator have access to several
default gulp theme tasks that provide the standard features required to
develop and build your theme (build, deploy, watch, etc.). You may,
however, want to run additional processes on your theme's files prior to
deploying the theme to the server---such as minifying your JavaScript
files. The Liferay Theme Generator's APIs expose a \texttt{hookFn}
property that lets you hook into the default gulp theme tasks to inject
your own logic.
Follow these steps to hook into the default Liferay theme tasks:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Identify the gulp task or sub task that you want to hook into or
overwrite. The tasks and their sub tasks are listed in their
\texttt{{[}task-name{]}.js} file in the
\href{https://github.com/liferay/liferay-js-themes-toolkit/tree/master/packages/liferay-theme-tasks/tasks}{\texttt{tasks}
folder} of the
\href{https://github.com/liferay/liferay-js-themes-toolkit/tree/master/packages/liferay-theme-tasks}{\texttt{liferay-theme-tasks}}
package. For example, the gulp \texttt{build} task and sub tasks are
defined in the
\href{https://github.com/liferay/liferay-js-themes-toolkit/blob/master/packages/liferay-theme-tasks/tasks/build.js\#L73-L92}{\texttt{build.js}
file}:
\begin{verbatim}
gulp.task('build', function(cb) {
runSequence(
'build:clean',
'build:base',
'build:src',
'build:web-inf',
'build:liferay-look-and-feel',
'build:hook',
'build:themelets',
'build:rename-css-dir',
'build:compile-css',
'build:fix-url-functions',
'build:move-compiled-css',
'build:remove-old-css-dir',
'build:fix-at-directives',
'build:r2',
'build:war',
cb
);
});
\end{verbatim}
\item
Open your theme's \texttt{gulpfile.js} file and locate the
\texttt{liferayThemeTasks.registerTasks()} method. This method
registers the default gulp theme tasks. Add the \texttt{hookFn}
property to the \texttt{registerTasks()} method's configuration
object, making sure to pass in the \texttt{gulp} instance:
\begin{verbatim}
liferayThemeTasks.registerTasks({
gulp: gulp,
hookFn: function(gulp) {
}
});
\end{verbatim}
\item
Inside the \texttt{hookFn()} function, use the \texttt{gulp.hook()}
method to specify the theme task or sub task that you want to hook
into. You can inject your code before or after a task by prefixing it
with the \texttt{before:} or \texttt{after:} keywords. Alternatively,
you can use the \texttt{gulp.task()} method to overwrite a gulp task.
Both methods have two parameters: the task or sub task you want to
hook into and a callback function that invokes \texttt{done} or
returns a stream with the logic that you want to inject. A few example
configuration patterns are shown below:
\begin{verbatim}
liferayThemeTasks.registerTasks({
gulp: gulp,
hookFn: function(gulp) {
gulp.hook('before:build:src', function(done) {
// Fires before build:src task
});
gulp.hook('after:build', function(done) {
// Fires after build task
});
gulp.task('build:base', function(done) {
// Overwrites build:base task
});
}
});
\end{verbatim}
\end{enumerate}
The example below fires before the \texttt{build:war} sub-task and reads
the JavaScript files in the theme's \texttt{build} folder, minifies them
with the \texttt{gulp-uglify} module, places them back in the
\texttt{./build/js} folder, invokes \texttt{done}, and finally logs that
the JavaScript was minified. To follow along, replace your theme's
\texttt{gulpfile.js} with the contents shown below, install the
\href{https://www.npmjs.com/package/gulp-uglify}{gulp-uglify} module and
the \href{https://www.npmjs.com/package/fancy-log}{fancy-log} module,
and run \texttt{gulp\ deploy}:
\begin{verbatim}
'use strict';
var gulp = require('gulp');
var log = require('fancy-log');
var uglify = require('gulp-uglify');
var liferayThemeTasks = require('liferay-theme-tasks');
liferayThemeTasks.registerTasks({
gulp: gulp,
hookFn: function(gulp) {
gulp.hook('before:build:war', function(done) {
// Fires before build `war` task
gulp.src('./build/js/*.js')
.pipe(uglify())
.pipe(gulp.dest('./build/js'))
.on('end', done);
log('Your JS is now minified...');
});
}
});
\end{verbatim}
You should see something similar to the output shown below:
\begin{verbatim}
[15:58:07] Finished 'build:r2' after 198 ms
[15:58:07] Starting 'build:war'...
[15:58:07] Your JS is now minified...
[15:58:07] Starting 'plugin:version'...
[15:58:07] Finished 'plugin:version' after 2.52 ms
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} The \texttt{hook} callback function must invoke the
\texttt{done} argument or return a stream.
\noindent\hrulefill
Now you know how to hook into and overwrite the default Liferay theme
tasks!
\section{Related Topics}\label{related-topics-64}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Installing
the Theme Generator and Creating a Theme}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Generating
Themelets}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Using
Developer Mode with Themes}
\end{itemize}
\chapter{Clay CSS and Themes}\label{clay-css-and-themes}
\href{https://liferay.design/lexicon/}{Lexicon} is a design language
that provides a common framework for building consistent UIs.
\href{https://clayui.com/docs/css-framework/scss.html}{Clay}, the web
implementation of Lexicon, is an extension of Bootstrap's open source
CSS Framework. Bootstrap is by far the most popular CSS framework on the
web. Built with Sass, Clay CSS fills the front-end gaps between
Bootstrap and the specific needs of Liferay DXP.
Bootstrap features have been extended to cover more use cases. Here are
some of the new components added by Clay CSS:
\begin{itemize}
\tightlist
\item
Aspect Ratio
\item
Cards
\item
Dropdown Wide and Dropdown Full
\item
Figures
\item
Nameplates
\item
Sidebar / Sidenav
\item
Stickers
\item
SVG Icons
\item
Timelines
\item
Toggles
\end{itemize}
Several reusable CSS patterns have also been added to help accomplish
time consuming tasks such as these:
\begin{itemize}
\tightlist
\item
truncating text
\item
content filling the remaining container width
\item
truncating text inside table cells
\item
table cells filling remaining container width and table cells only
being as wide as their content
\item
open and close icons inside collapsible panels
\item
nested vertical navigations
\item
slide out panels
\item
notification icons/messages
\item
vertical alignment of content
\end{itemize}
\href{https://clayui.com/}{Clay CSS} is bundled with two sub-themes:
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-theme/frontend-theme-styled/src/main/resources/META-INF/resources/_styled/css/clay}{Clay
Base} and
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-theme/frontend-theme-styled/src/main/resources/META-INF/resources/_styled/css/clay/atlas}{Atlas}.
Clay Base is Liferay DXP's Bootstrap API extension. It adds all the
features and components you need and inherits Bootstrap's styles. As a
result, Clay Base is fully compatible with
\href{/docs/7-2/frameworks/-/knowledge_base/f/integrating-third-party-themes-with-clay}{third
party themes} that leverage Bootstrap's Sass variable API.
Atlas is Liferay DXP's custom Bootstrap theme that is used in the
Classic Theme. Its purpose is to overwrite and manipulate Bootstrap and
Clay Base to create its classic look and feel. Atlas is equivalent to
installing a Bootstrap third party theme.
\noindent\hrulefill
\textbf{Note:} It is not recommended to integrate third party themes
with Atlas, as it adds variables and styles that are outside the scope
of Bootstrap's API.
\noindent\hrulefill
This section covers these topics:
\begin{itemize}
\tightlist
\item
Customizing the Atlas and Clay base themes
\item
Integrating third party themes with Clay
\end{itemize}
\chapter{Customizing Atlas and Clay Base Themes in Liferay
DXP}\label{customizing-atlas-and-clay-base-themes-in-liferay-dxp}
Whether you're customizing the Atlas or Clay base theme, the process is,
for the most part, the same. Follow these steps. If you're customizing
the Clay base theme, skip to step 3.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
By default, Clay base is imported into the theme. If you're
overwriting Atlas, add a file named \texttt{clay.scss} to your theme's
\texttt{/src/css/} folder and import \texttt{clay/atlas} instead:
\begin{verbatim}
@import "clay/atlas";
\end{verbatim}
\item
By default, Clay base variables are imported into the theme. If you're
overwriting Atlas, add an \texttt{\_imports.scss} file to your theme's
\texttt{/src/css/} folder and import Atlas variables instead:
\begin{verbatim}
@import "bourbon";
@import "mixins";
@import "compat/mixins";
@import "clay/atlas-variables";
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Bourbon mixins are deprecated as of 7.0 and will be
removed in the next major release. We recommend you use Clay mixins
instead. To use Clay mixins, follow the instructions in
[Using Clay Mixins in Your Theme](/docs/7-2/frameworks/-/knowledge_base/f/using-clay-mixins-in-your-theme)
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
Add a file named \texttt{\_clay\_variables.scss}. Place your Atlas,
Bootstrap, and Clay Base variable modifications in this file.
\end{enumerate}
Great! Now you know how to customize the Atlas and Clay base themes.
\section{Related Topics}\label{related-topics-65}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/integrating-third-party-themes-with-clay}{Integrating
Third Party Themes with Clay}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-clay-mixins-in-your-theme}{Using
Clay Mixins in Your Theme}
\end{itemize}
\chapter{Integrating Third Party Themes with
Clay}\label{integrating-third-party-themes-with-clay}
\href{https://github.com/liferay/liferay-portal/tree/7.1.x/modules/apps/frontend-theme/frontend-theme-styled/src/main/resources/META-INF/resources/_styled/css/clay}{Clay
Base} provides all the features and components your theme needs and
inherits Bootstrap's styles. As a result, Clay Base is fully compatible
with third party themes that leverage Bootstrap's Sass variable API.
The
\href{https://github.com/liferay/liferay-portal/tree/7.1.x/modules/apps/frontend-theme/frontend-theme-styled}{Styled
Theme} uses Clay Base to provide its styles and components. Therefore,
as a best practice, you should use the Styled base theme to integrate
third party themes.
\noindent\hrulefill
\textbf{Note:} You can purchase third party themes from the
\href{https://web.liferay.com/marketplace}{Liferay Marketplace}. Third
party themes must be built with Sass to be compatible. \textbf{Make
sure} Sass files are included before making any theme purchase.
\noindent\hrulefill
Follow these steps to integrate a third party theme with Clay Base:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a new theme with the Styled Theme as its base. This is the
default base theme for newly created themes, so no further action is
required. This provides the Clay Base files you need.
\item
In the theme's \texttt{/src/css/} folder, add a file named
\texttt{\_clay\_variables.scss}. Place your Atlas, Bootstrap, and Clay
Base variable modifications in this file.
\item
Create a folder inside \texttt{/src/css/} to house your third party
theme (e.g.~\texttt{/src/css/my-third-party-theme/})
\item
Copy the CSS contents of the theme to the folder you just created.
\item
In \texttt{\_clay\_variables.scss}, import the file containing the
theme variables. For example,
\texttt{@import\ "my-third-party-theme/variables.scss";}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You may omit the leading underscore when importing Sass files.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
In \texttt{\_custom.scss}, import the file containing the CSS. For
example, \texttt{@import\ "my-third-party-theme/main.scss";}
\end{enumerate}
Now you know how to integrate third party themes with Clay Base!
\section{Related Topics}\label{related-topics-66}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-atlas-and-clay-base-themes}{Customizing
Atlas and Clay Base Themes}
\end{itemize}
\chapter{Using Clay Icons in a Theme}\label{using-clay-icons-in-a-theme}
To use Clay icons in your themes, you must use the
\href{/docs/7-2/reference/-/knowledge_base/r/freemarker-taglib-macros}{clay
taglib macro}. If you want to use Clay icons in your portlets, follow
the steps in the
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay taglib
icons} article. To use Clay icons in your theme, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the FreeMarker theme template you want to use the Clay icon in.
\item
Use the \texttt{@clay{[}"icon"{]}} macro and specify the icon with the
\texttt{symbol} attribute, as shown in the pattern below:
\begin{verbatim}
<@clay["icon"] symbol="icon-name" />
\end{verbatim}
The full list of icons can be found on
\href{https://clayui.com/docs/components/icon.html}{ClayUI's site}
(CSS/Markup tab). Here is an example configuration for a Facebook
social media icon:
\begin{verbatim}
Facebook
<@clay["icon"] symbol="social-facebook" />
\end{verbatim}
\end{enumerate}
Great! Now you know how to use Clay icons in your theme.
\section{Related Topics}\label{related-topics-67}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/freemarker-taglib-macros}{FreeMarker
Taglib Macros}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-atlas-and-clay-base-themes}{Customizing
Atlas and Clay Base Themes}
\end{itemize}
\chapter{Using Clay Mixins in Your
Theme}\label{using-clay-mixins-in-your-theme}
Bourbon mixins are deprecated as of 7.0 and will be removed in the next
major release. We recommend you use Clay mixins instead. Follow these
steps to use Clay mixins in your theme:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{clay-css} dependency to the theme's
\texttt{package.json}:
\begin{verbatim}
"dependencies":{
"clay-css": "^2.18.0",
}
\end{verbatim}
\item
Delete \texttt{\_imports.scss} if you modified it.
\item
Import the library into the theme's \texttt{main.scss} file:
\begin{verbatim}
@import 'node_modules/clay-css/src/scss/atlas-variables'
\end{verbatim}
or import the base-variables if you want to use Clay Base instead:
\begin{verbatim}
@import 'node_modules/clay-css/src/scss/base-variables'
\end{verbatim}
\end{enumerate}
Great! Now you know how to use Clay mixins in your theme!
\section{Related Topics}\label{related-topics-68}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-atlas-and-clay-base-themes}{Customizing
Atlas and Clay Base Theme}
\end{itemize}
\chapter{Theming Portlets}\label{theming-portlets}
Although you can individually style a portlet via the theme's CSS or the
portlet's
\href{/docs/7-2/user/-/knowledge_base/u/look-and-feel-configuration}{Look
and Feel Configuration} menu, you may want to modify the default look
and feel for all portlets in your site. A portlet's template--its
container, CSS classes, and overall HTML Markup--is defined via the
theme's \texttt{portlet.ftl} file. To provide a custom style for all
portlets, use the CSS classes in this file for the various container
elements along with the portlet decorators to achieve the desired look
and feel. Be cautious: changes to \texttt{portlet.ftl} affect all the
portlets in your site when the theme is applied.
To help you with your bearings as you modify your portlet's template,
below is a quick look at the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-theme/frontend-theme-classic/src/templates/portlet.ftl}{\texttt{portlet.ftl}}
file that's included in 7.0's Classic theme.
\begin{verbatim}
<#assign
portlet_display = portletDisplay
portlet_back_url = htmlUtil.escapeHREF(portlet_display.getURLBack())
portlet_content_css_class = "portlet-content"
portlet_display_name = htmlUtil.escape(portlet_display.getPortletDisplayName())
portlet_display_root_portlet_id = htmlUtil.escapeAttribute(portlet_display.getRootPortletId())
portlet_id = htmlUtil.escapeAttribute(portlet_display.getId())
portlet_title = htmlUtil.escape(portlet_display.getTitle())
/>
\end{verbatim}
These variables are described in the table below:
\textbf{Portlet FTL Variables}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{portletDisplay} & Fetched from the \texttt{themeDisplay} object,
contains information about the portlet \\
\texttt{portlet\_back\_url} & URL to return to the previous page when
the portlet \texttt{WindowState} is maximized \\
\texttt{portlet\_display\_name} & The ``friendly'' name of the portlet
as displayed in the GUI \\
\texttt{portlet\_display\_root\_portlet\_id} & The root portlet ID of
the portlet \\
\texttt{portlet\_id} & The ID of the portlet (not the same as the
portlet namespace) \\
\texttt{portlet\_title} & The portlet name set in the portlet Java class
(usually from a \texttt{Keys.java} class) \\
\end{longtable}
\noindent\hrulefill
Next, a condition checks if the portlet header should be displayed. If
the portlet has a portlet toolbar (Configuration, Permissions, Look and
Feel), the condition is true and the header is displayed:
\begin{verbatim}
<#if portlet_display.isPortletDecorate() && !portlet_display.isStateMax()
&& portlet_display.getPortletConfigurationIconMenu()??
&& portlet_display.getPortletToolbar()??>
\end{verbatim}
You can use a similar pattern if you want to dynamically show portions
of the portlet's UI.
Next, the portlet title menus are defined. These are used in portlets
that let you add resources (Web Content Display, Media Gallery,
Documents and Media):
\begin{verbatim}
portlet_title_menus = portlet_toolbar.getPortletTitleMenus(portlet_display_root_portlet_id, renderRequest, renderResponse)
\end{verbatim}
The configuration below contains the information for the configuration
menu (Configuration, Permissions, Look and Feel):
\begin{verbatim}
portlet_configuration_icons = portlet_configuration_icon_menu.getPortletConfigurationIcons(portlet_display_root_portlet_id, renderRequest, renderResponse)
\end{verbatim}
The rest of the file contains the HTML markup for the portlet topper and
the portlet content. This section barely scratches the surface of the
\texttt{portlet.ftl} file. You must examine the \texttt{portlet.ftl}
file yourself and determine what elements and classes need updated for
your theme and site.
Now that you are more familiar with your theme's \texttt{portlet.ftl}
file, you can learn the role Portlet Decorators play in the overall look
and feel of your portlets.
\section{Portlet Decorators}\label{portlet-decorators}
Portlet Decorators modify the style of the application wrapper. Themes
come bundled with three default portlet decorators, defined in
\texttt{liferay-look-and-feel.xml}:
\begin{itemize}
\item
Decorate: this is the default Application Decorator when using the
Classic theme. It wraps the application in a white box with a border,
and displays the title at the top.
\begin{figure}
\centering
\includegraphics{./images/application-decorator-decorate.png}
\caption{The Classic theme's Decorate Application Decorator wraps the
portlet in a white box.}
\end{figure}
\item
Borderless: this decorator shows the title at the top, but does not
display a wrapping box.
\begin{figure}
\centering
\includegraphics{./images/application-decorator-borderless.png}
\caption{The Classic theme's Borderless Application Decorator displays
the application's custom title.}
\end{figure}
\item
Barebone: this decorator displays the bare application content,
showing neither the wrapping box nor the custom application title.
\begin{figure}
\centering
\includegraphics{./images/application-decorator-barebone.png}
\caption{The Classic theme's Barebone Application Decorator displays
only the application's content.}
\end{figure}
\end{itemize}
\noindent\hrulefill
\textbf{Note:} Upgrading to Liferay DXP assigns the \emph{borderless}
decorator automatically to those portlets that had the \emph{Show
Borders} option set to false in previous versions of Liferay.
\noindent\hrulefill
This section covers these topics:
\begin{itemize}
\tightlist
\item
Embedding Portlets in Themes
\end{itemize}
\chapter{Embedding Portlets in
Themes}\label{embedding-portlets-in-themes}
You may occasionally want to embed a portlet in a theme, making the
portlet visible on all pages where the theme is used. Since there are
numerous drawbacks to hard-coding a specific portlet into place, the
\emph{Portlet Providers} framework offers an alternative that displays
the appropriate portlet based on a given entity type and action.
The first thing you should do is open the template file for which you
want to declare an embedded portlet. For example, the
\texttt{portal\_normal.ftl} template file is a popular place to declare
embedded portlets. To avoid problems, it's usually best to embed
portlets with an entity type and action, but you may encounter
circumstances where you'll want to hard code it by portlet name. Both
methods are covered in this section. These topics are covered:
\begin{itemize}
\tightlist
\item
Embedding a portlet by entity type and action
\item
Embedding a portlet by instance name and ID
\end{itemize}
\chapter{Embedding Portlets in Themes by Entity Type and
Action}\label{embedding-portlets-in-themes-by-entity-type-and-action}
In this article, you'll learn how to declare an entity type and action
in a custom theme, and you'll create a module that finds the correct
portlet to use based on those given parameters. Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Insert a declaration where you want the portlet embedded. This
declaration expects two parameters: the type of action and the class
name of the entity type the portlet should handle. An example
configuration is shown below:
\begin{verbatim}
<@liferay_portlet["runtime"]
portletProviderAction=portletProviderAction.VIEW
portletProviderClassName="com.liferay.portal.kernel.servlet.taglib.ui.LanguageEntry"
/>
\end{verbatim}
This example declares that the theme is requesting to view language
entries. Liferay DXP determines which deployed portlet to use in this
case by providing the portlet with the highest service ranking.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** In some cases, a default portlet is already provided to fulfill
certain requests. You can override the default portlet with your custom
portlet by specifying a higher service rank. To do this, set the following
property in your class' `@Component` declaration:
property= {"service.ranking:Integer=20"}
Make sure you set the service ranking higher than the default portlet being
used.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
The Portal is not yet configured to handle this request. You must
create a module that can find the portlet that fits the theme's
request.
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a module}.
\item
Create a unique package name in the module's \texttt{src} directory,
and create a new Java class in that package. To follow naming
conventions, name the class based on the entity type and action type,
followed by \emph{PortletProvider} (e.g.,
\texttt{SiteNavigationLanguageEntryViewPortletProvider}). The class
should extend the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BasePortletProvider.html}{\texttt{BasePortletProvider}}
class and implement the appropriate portlet provider interface based
on the action you chose in your theme (e.g.,
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/ViewPortletProvider.html}{\texttt{ViewPortletProvider}},
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BrowsePortletProvider.html}{\texttt{BrowsePortletProvider}},
etc.).
\item
Directly above the class's declaration, insert the following
annotation:
\begin{verbatim}
@Component(
immediate = true,
property = {"model.class.name=CLASS_NAME"},
service = INTERFACE.class
)
\end{verbatim}
The \texttt{property} element must match the entity type you specified
in your theme declaration (e.g.,
\texttt{com.liferay.portal.kernel.servlet.taglib.ui.LanguageEntry}).
Also, your \texttt{service} element should match the interface you're
implementing (e.g., \texttt{ViewPortletProvider.class}).
\item
Specify the methods you want to implement. Make sure to retrieve the
portlet ID and page ID that should be provided when this service is
called by your theme.
A common use case is to implement the \texttt{getPortletId()} and
\texttt{getPlid(ThemeDisplay)} methods. Below is an example
configuration:
\begin{verbatim}
@Override
public String getPortletName() {
return SiteNavigationLanguagePortletKeys.SITE_NAVIGATION_LANGUAGE;
}
@Override
public PortletURL getPortletURL(
HttpServletRequest httpServletRequest, Group group)
throws PortalException {
return PortletURLFactoryUtil.create(
httpServletRequest, getPortletName(), PortletRequest.RENDER_PHASE);
}
/**
* @deprecated As of Judson (7.1.x)
*/
@Deprecated
@Override
protected long getPlid(ThemeDisplay themeDisplay) throws PortalException {
return themeDisplay.getPlid();
}
\end{verbatim}
This returns the portlet ID and the PLID, which is the ID that
uniquely identifies a page used by your theme. By retrieving these,
your theme will know which portlet to use, and which page to use it
on.
\item
Generate the module's JAR file and copy it to your app server's
\texttt{osgi/modules/} folder. Once the module is installed and
activated in your Portal's service registry, your embedded portlet is
available for use wherever your theme is used.
\end{enumerate}
Awesome! You successfully requested a portlet based on the entity and
action types required, and created and deployed a module that retrieves
the portlet and embeds it in your theme.
\section{Related Topics}\label{related-topics-69}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes}{Embedding
Portlets in Themes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}
\end{itemize}
\chapter{Embedding a Portlet by Portlet
Name}\label{embedding-a-portlet-by-portlet-name}
If you'd like to embed a specific portlet in the theme, you can hard
code it by providing its instance ID and name. Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the FreeMarker theme template that you want to embed the portlet
in. (\texttt{portal\_normal.ftl} is a good choice.
\item
Add the \texttt{liferay\_portlet{[}"runtime"{]}} macro to the
template, as shown below. The portlet name \textbf{must} be the same
as \texttt{javax.portlet.name}'s value.
\begin{verbatim}
<@liferay_portlet["runtime"]
instanceId="INSTANCE_ID"
portletName="PORTLET_NAME"
/>
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} If your portlet is instanceable, an instance ID must be
provided; otherwise, you can remove this line. To set your portlet to
non-instanceable, set the property
\texttt{com.liferay.portlet.instanceable} in the component annotation of
your portlet to \texttt{false}.
\noindent\hrulefill
Here's an example of an embedded portlet declaration that uses the
portlet name to embed a web content portlet:
\begin{verbatim}
<@liferay_portlet["runtime"]
portletName="com_liferay_journal_content_web_portlet_JournalContentPortlet"
/>
\end{verbatim}
Great! Now you know how to embed a portlet in your theme's by their name
and ID.
\section{Related Topics}\label{related-topics-70}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes-by-entity-type-and-action}{Embedding
Portlets in Themes by Entity Type and Action}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}
\end{itemize}
\chapter{Setting Default Preferences for an Embedded
Portlet}\label{setting-default-preferences-for-an-embedded-portlet}
Follow these steps to set default portlet preferences for an embedded
portlet:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Retrieve portlet preferences using the
\texttt{freeMarkerPortletPreferences} object. The example below
retrieves the \texttt{barebone}:
\begin{verbatim}
<#assign preferences = freeMarkerPortletPreferences.getPreferences(
"portletSetupPortletDecoratorId", "barebone"
) />
\end{verbatim}
\item
Set the \texttt{defaultPreferences} attribute of the embedded portlet
to the \texttt{freeMarkerPortletPreferences} object you just
configured:
\begin{verbatim}
<@liferay_portlet["runtime"]
defaultPreferences="${preferences}"
portletName="com_liferay_login_web_portlet_LoginPortlet"
/>
\end{verbatim}
\end{enumerate}
Below are some additional attributes you can define for embedded
portlets:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Preference
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\textbf{defaultPreferences} & A string of Portlet Preferences for the
application. This includes look and feel configurations. \\
\textbf{instanceId} & The instance ID for the app, if the application is
instanceable. \\
\textbf{persistSettings} & Whether to have an application use its
default settings, which will persist across layouts. The default value
is \emph{true}. \\
\textbf{settingsScope} & Specifies which settings to use for the
application. The default value is \texttt{portletInstance}, but it can
be set to \texttt{group} or \texttt{company}. \\
\end{longtable}
\noindent\hrulefill
Now you know how to set default preferences for embedded portlets!
\section{Related Topics}\label{related-topics-71}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-a-portlet-by-portlet-name}{Embedding
Portlets by Name}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}
\end{itemize}
\chapter{Importing Resources with a
Theme}\label{importing-resources-with-a-theme}
To truly appreciate a theme, you must view it with content. Showcasing a
theme in the proper context is key to communicating the true intentions
of its design. Who better to do this than the theme's designer?
Designers can provide a sample context that optimizes the design of
their themes. The Resources Importer does this for you.
\noindent\hrulefill
\textbf{Important:} The Resources Importer is deprecated as of 7.0 7.1.
\noindent\hrulefill
The Resources Importer module lets theme developers import files and web
content with a theme. Administrators can use the site or site template
created by the Resources Importer to showcase the theme. In fact, all
standalone themes that are uploaded to Liferay Marketplace \textbf{must
use} the Resources Importer. This ensures a uniform experience for
Marketplace users: a user can download a theme from Marketplace, install
it, go to Sites or Site Templates in the Control Panel and immediately
see their new theme in action.
\section{Organizing Your Resources}\label{organizing-your-resources}
Add your resources to the theme's
\texttt{/src/WEB-INF/src/resources-importer} folder as outlined below:
\begin{itemize}
\tightlist
\item
\texttt{{[}theme-name{]}/src/WEB-INF/src/resources-importer/}
\begin{itemize}
\tightlist
\item
\texttt{sitemap.json} - defines the pages, layout templates, and
portlets
\item
\texttt{assets.json} - (optional) specifies details on the assets
\item
\texttt{document\_library/}
\begin{itemize}
\tightlist
\item
\texttt{documents/} - contains documents and media files (assets)
\end{itemize}
\item
\texttt{journal/}
\begin{itemize}
\tightlist
\item
\texttt{articles/} - contains web content (HTML) and folders
grouping web content articles (XML) by template. Each folder name
must match the file name of the corresponding template. For
example, create folder \texttt{Template\ 1/} to hold an article
based on template file \texttt{Template\ 1.ftl}.
\item
\texttt{structures/} - contains structures (JSON) and folders of
child structures. Each folder name must match the file name of the
corresponding parent structure. For example, create folder
\texttt{Structure\ 1/} to hold a child of structure file
\texttt{Structure\ 1.json}.
\item
\texttt{templates/} - groups templates (VM or FTL) into folders by
structure. Each folder name must match the file name of the
corresponding structure. For example, create folder
\texttt{Structure\ 1/} to hold a template for structure file
\texttt{Structure\ 1.json}.
\end{itemize}
\end{itemize}
\end{itemize}
Using the Resources Importer involves the following steps:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/preparing-and-organizing-web-content-for-the-resources-importer}{Preparing
and Organizing Resources}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-sitemap-for-the-resources-importer}{Creating
a Sitemap for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-assets-for-the-resources-importer}{Defining
Assets for the Resources Importer} (optional)
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-where-to-import-your-themes-resources}{Specifying
Where to Import Your Theme's Resources}
\end{itemize}
This section explains how to use the Resources Importer to import
resources with your theme.
\chapter{Creating a Sitemap for the Resources
Importer}\label{creating-a-sitemap-for-the-resources-importer}
You have two options for specifying resources to be imported with your
theme: a sitemap or an
\href{/docs/7-2/frameworks/-/knowledge_base/f/archiving-your-sites-resources}{archive
LAR file}. Using a \texttt{sitemap.json} file is the most flexible
approach, so we recommend it; unlike LAR files, a \texttt{sitemap.json}
can be created in one version of Liferay DXP and used in another. LAR
files are version-specific, and can only be imported in the same version
in which they were created.
\noindent\hrulefill
\textbf{Important:} The Resources Importer is deprecated as of 7.0 7.1.
\noindent\hrulefill
The \texttt{sitemap.json} specifies the site pages, layout templates,
web content, assets, and portlet configurations provided with the theme.
This file describes the contents and hierarchy of the site to import as
a site or site template. If you're developing themes for Liferay
Marketplace, you must use the \texttt{sitemap.json} to specify resources
to be imported with your theme. Even if you're not familiar with JSON,
the \texttt{sitemap.json} file is easy to understand. An example
\texttt{sitemap.json} file is shown below:
\begin{verbatim}
{
"layoutTemplateId": "2_columns_ii",
"privatePages": [
{
"friendlyURL": "/private-page",
"name": "Private Page",
"title": "Private Page"
}
],
"publicPages": [
{
"columns": [
[
{
"portletId": "com_liferay_login_web_portlet_LoginPortlet"
},
{
"portletId":
"com_liferay_site_navigation_menu_web_portlet_SiteNavigationMenuPortlet"
},
{
"portletId":
"com_liferay_journal_content_web_portlet_JournalContentPortlet",
"portletPreferences": {
"articleId": "Without Border.html",
"groupId": "${groupId}",
"portletSetupPortletDecoratorId": "borderless"
}
},
{
"portletId": "com_liferay_journal_content_web_portlet_JournalContentPortlet",
"portletPreferences": {
"articleId": "Custom Title.html",
"groupId": "${groupId}",
"portletSetupPortletDecoratorId": "decorate",
"portletSetupTitle_en_US": "Web Content Display with Custom Title",
"portletSetupUseCustomTitle": "true"
}
}
],
[
{
"portletId": "com_liferay_hello_world_web_portlet_HelloWorldPortlet"
},
{
"portletId":
"com_liferay_site_navigation_menu_web_portlet_SiteNavigationMenuPortlet_INSTANCE_${groupId}",
"portletPreferences": {
"displayStyle": "[custom]",
"headerType": "root-layout",
"includedLayouts": "all",
"nestedChildren": "1",
"rootLayoutLevel": "3",
"rootLayoutType": "relative"
}
},
"Web Content with Image.html",
{
"portletId": "com_liferay_nested_portlets_web_portlet_NestedPortletsPortlet",
"portletPreferences": {
"columns": [
[
{
"portletId":
"com_liferay_journal_content_web_portlet_JournalContentPortlet",
"portletPreferences": {
"articleId": "Child Web Content 1.xml",
"groupId": "${groupId}",
"portletSetupPortletDecoratorId": "decorate",
"portletSetupTitle_en_US":
"Web Content Display with Child Structure 1",
"portletSetupUseCustomTitle": "true"
}
}
],
[
{
"portletId":
"com_liferay_journal_content_web_portlet_JournalContentPortlet",
"portletPreferences": {
"articleId": "Child Web Content 2.xml",
"groupId": "${groupId}",
"portletSetupPortletDecoratorId": "decorate",
"portletSetupTitle_en_US":
"Web Content Display with Child Structure 2",
"portletSetupUseCustomTitle": "true"
}
}
]
],
"layoutTemplateId": "2_columns_i"
}
}
]
],
"friendlyURL": "/home",
"nameMap": {
"en_US": "Welcome",
"fr_FR": "Bienvenue"
},
"title": "Welcome"
},
{
"columns": [
[
{
"portletId": "com_liferay_login_web_portlet_LoginPortlet"
}
],
[
{
"portletId": "com_liferay_hello_world_web_portlet_HelloWorldPortlet"
}
]
],
"friendlyURL": "/layout-prototypes-parent-page",
"layouts": [
{
"friendlyURL": "/layout-prototypes-page-1",
"layoutPrototypeLinkEnabled": "true",
"layoutPrototypeUuid": "371647ba-3649-4039-bfe6-ae32cf404737",
"name": "Layout Prototypes Page 1",
"title": "Layout Prototypes Page 1"
},
{
"friendlyURL": "/layout-prototypes-page-2",
"layoutPrototypeUuid": "c98067d0-fc10-9556-7364-238d39693bc4",
"name": "Layout Prototypes Page 2",
"title": "Layout Prototypes Page 2"
}
],
"name": "Layout Prototypes",
"title": "Layout Prototypes"
},
{
"columns": [
[
{
"portletId": "com_liferay_login_web_portlet_LoginPortlet"
}
],
[
{
"portletId": "com_liferay_hello_world_web_portlet_HelloWorldPortlet"
}
]
],
"friendlyURL": "/parent-page",
"layouts": [
{
"friendlyURL": "/child-page-1",
"name": "Child Page 1",
"title": "Child Page 1"
},
{
"friendlyURL": "/child-page-2",
"name": "Child Page 2",
"title": "Child Page 2"
}
],
"name": "Parent Page",
"title": "Parent Page"
},
{
"friendlyURL": "/url-page",
"name": "URL Page",
"title": "URL Page",
"type": "url"
},
{
"friendlyURL": "/link-page",
"name": "Link to another Page",
"title": "Link to another Page",
"type": "link_to_layout",
"typeSettings": "linkToLayoutId=1"
},
{
"friendlyURL": "/hidden-page",
"name": "Hidden Page",
"title": "Hidden Page",
"hidden": "true"
}
]
}
\end{verbatim}
If you don't understand the sitemap at this point, don't worry. This
section covers how to create a sitemap for your theme, from
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-layout-templates-and-pages-in-a-sitemap}{defining
pages} to
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-portlets-in-a-sitemap}{defining
portlets}.
\chapter{Defining Layout Templates and Pages in a
Sitemap}\label{defining-layout-templates-and-pages-in-a-sitemap}
A sitemap defines the layouts---pages---and layout templates that your
site or site template uses. Follow these steps to define pages and
layout templates in your theme's \texttt{sitemap.json}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Define a default layout template ID so the target site or site
template can reference the layout template to use for its pages. When
defined outside the scope of a page, the \texttt{layoutTemplateId}
sets the default layout template for the theme's pages:
\begin{verbatim}
{
"layoutTemplateId": "2_columns_ii",
"publicPages": [
{
"friendlyURL": "/my-page",
"name": "My Page",
"title": "My Page"
}
]
}
\end{verbatim}
You can override this by defining a layout template inside a page
configuration. In the example below, the Hidden Page and the Welcome
page both use the default \texttt{2\_columns\_ii} layout template,
while the Custom Layout Page overrides the default layout template and
uses the \texttt{2\_columns\_i} layout template instead:
\begin{verbatim}
{
"layoutTemplateId":"2_columns_ii",
"publicPages": [
{
"friendlyURL": "/welcome-page",
"name": "Welcome",
"title": "Welcome"
},
{
"friendlyURL": "/custom-layout-page",
"name": "Custom Layout Page",
"title": "Custom Layout Page",
"layoutTemplateId": "2_columns_i"
},
{
"friendlyURL": "/hidden-page",
"name": "Hidden Page",
"title": "Hidden Page",
"hidden": "true"
}
]
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Pages are imported into a site template by default. Site templates
only support the importing of either public page sets or private page sets, not
both.
If you want to import both public and private page sets, as shown in the example
`sitemap.json` below, you must
[import your resources into a site](/docs/7-2/frameworks/-/knowledge_base/f/specifying-where-to-import-your-themes-resources).
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Follow the pattern below to specify the public and (optionally)
private pages for your theme. You can specify a name for a page,
title, friendly URL, whether it is hidden, and much more. The example
below defines a default layout template and both public and private
page sets to import into a site. See
\href{/docs/7-2/reference/-/knowledge_base/r/sitemap-page-configuration-options}{Sitemap
Page Configuration Options} for a full list of the available options.
\begin{verbatim}
{
"layoutTemplateId": "2_columns_ii",
"privatePages": [
{
"friendlyURL": "/private-page",
"name": "Private Page",
"title": "Private Page"
}
],
"publicPages": [
{
"friendlyURL": "/welcome-page",
"nameMap": {
"en_US": "Welcome",
"fr_FR": "Bienvenue"
},
"title": "Welcome"
},
{
"friendlyURL": "/custom-layout-page",
"name": "Custom Layout Page",
"title": "Custom Layout Page",
"layoutTemplateId": "2_columns_i"
},
{
"friendlyURL": "/hidden-page",
"name": "Hidden Page",
"title": "Hidden Page",
"hidden": "true"
}
]
}
\end{verbatim}
You can create child pages by configuring the \texttt{layouts} element
for a page configuration:
\begin{verbatim}
{
"friendlyURL": "/parent-page",
"layouts": [
{
"friendlyURL": "/child-page-1",
"name": "Child Page 1",
"title": "Child Page 1"
},
{
"friendlyURL": "/child-page-2",
"name": "Child Page 2",
"title": "Child Page 2"
}
],
"name": "Parent Page",
"title": "Parent Page"
}
\end{verbatim}
\end{enumerate}
Great! Now you know how to configure pages for the Resources Importer.
\section{Related Topics}\label{related-topics-72}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/preparing-and-organizing-web-content-for-the-resources-importer}{Preparing
and Organizing Web Content for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-portlets-in-a-sitemap}{Defining
Portlets in a Sitemap}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-where-to-import-your-themes-resources}{Specifying
Where to Import Your Theme's Resources}
\end{itemize}
\chapter{Defining Portlets in a
Sitemap}\label{defining-portlets-in-a-sitemap}
You can embed portlets in a sitemap for the pages you define. You can
embed them with the default settings or provide portlet preferences for
a more custom look and feel. This tutorial covers both these options.
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Note the portlet's ID. This is the \texttt{javax.portlet.name}
attribute of the portlet spec. For convenience, The IDs for portlets
available out-of-the-box are listed in the
\href{/docs/7-1/reference/-/knowledge_base/r/fully-qualified-portlet-ids}{Fully
Qualified Portlet IDs} reference guide. For custom portlets, this
property is listed in the portlet class as the
\texttt{javax.portlet.name=} service property.
\item
List the portlet ID in the column of the layout you want to display
the portlet on. An example configuration is shown below:
\begin{verbatim}
{
"layoutTemplateId": "2_columns_ii",
"publicPages": [
{
"columns": [
[
{
"portletId": "com_liferay_login_web_portlet_LoginPortlet"
},
{
"portletId":
"com_liferay_site_navigation_menu_web_portlet_SiteNavigationMenuPortlet"
}
],
[
{
"portletId": "com_liferay_hello_world_web_portlet_HelloWorldPortlet"
}
]
],
"friendlyURL": "/home",
"nameMap": {
"en_US": "Welcome",
"fr_FR": "Bienvenue"
},
"title": "Welcome"
}
]
}
\end{verbatim}
This approach embeds the portlet with its default settings. To
customize the portlet, you must configure the portlet's preferences,
as described in the next step.
\item
Optionally specify portlet preferences for a portlet with the
\texttt{portletPreferences} key. Below is an example for the Web
Content Display portlet:
\begin{verbatim}
{
"portletId": "com_liferay_journal_content_web_portlet_JournalContentPortlet",
"portletPreferences": {
"articleId": "Custom Title.xml",
"groupId": "${groupId}",
"portletSetupPortletDecoratorId": "decorate",
"portletSetupTitle_en_US": "Web Content Display with Custom Title",
"portletSetupUseCustomTitle": "true"
}
}
\end{verbatim}
\textbf{portletSetupPortletDecoratorId:} Specifies the \href{}{portlet
decorator} to use for the portlet (\texttt{borderless}
\textbar\textbar{} \texttt{barebone} \textbar\textbar{}
\texttt{decorate}). See
\href{/docs/7-2/frameworks/-/knowledge_base/f/setting-default-preferences-for-an-embedded-portlet}{Setting
Default Preference for An Embedded Portlet} for more information.
\end{enumerate}
\noindent\hrulefill
\textbf{Tip:} You can specify an
\href{/docs/7-1/user/-/knowledge_base/u/styling-apps-and-assets}{application
display template} (ADT) for a portlet in the \texttt{sitemap.json} file
by setting the \texttt{displayStyle} and \texttt{displayStyleGroupId}
portlet preferences, as shown in the example below:
\begin{verbatim}
"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
"portletPreferences": {
"displayStyleGroupId": "10197",
"displayStyle": "ddmTemplate_6fe4851b-53bc-4ca7-868a-c836982836f4"
}
\end{verbatim}
\noindent\hrulefill
Portlet preferences are unique to each portlet, so first you must
determine which preferences you want to configure. There are two ways to
determine the proper key/value pair for a portlet preference. The first
is to set the portlet preference manually, and then check the values in
the \texttt{portletPreferences.preferences} column of the database as a
hint for what to configure in your \texttt{sitemap.json}. For example,
you can configure the Asset Publisher to display assets that match the
specified asset tags, using the following configuration:
\begin{verbatim}
"queryName0": "assetTags",
"queryValues0": "MyAssetTagName"
\end{verbatim}
Another approach is to search each app in your bundle for the keyword
\texttt{preferences-\/-}. This returns some of the app's JSPs that have
the portlet preferences defined for the portlet.
\noindent\hrulefill
\textbf{Note:} Portlet preferences that require an existing
configuration, such as a tag or category, may require you to create the
configuration on the Global site first, so that the Resources Importer
finds a match when deployed with the theme.
\noindent\hrulefill
\section{Related Topics}\label{related-topics-73}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/preparing-and-organizing-web-content-for-the-resources-importer}{Preparing
and Organizing Web Content for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-layout-templates-and-pages-in-a-sitemap}{Defining
Layout Templates and Pages in a Sitemap}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-where-to-import-your-themes-resources}{Specifying
Where to Import Your Theme's Resources}
\end{itemize}
\chapter{Retrieving Portlet IDs with the Gogo
Shell}\label{retrieving-portlet-ids-with-the-gogo-shell}
To
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-portlets-in-a-sitemap}{include
a portlet in a sitemap}, you must have its ID. For convenience, IDs for
out-of-the-box portlets appear in the
\href{/docs/7-2/reference/-/knowledge_base/r/fully-qualified-portlet-ids}{Fully
Qualified Portlet IDs} reference guide. If you've installed purchased or
developed portlets, you can retrieve their IDs using the Gogo Shell, as
this tutorial instructs.
Follow these steps to use the Gogo Shell to retrieve a portlet ID:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the Control Panel and go to Configuration → Gogo Shell.
\item
Run the command \texttt{lb\ {[}app\ prefix{]}}, and locate the app's
web bundle. For example, run \texttt{lb\ blogs} to find the blogs web
bundle.
\begin{verbatim}
100|Active | 10|Liferay Blogs Web (3.0.7)
\end{verbatim}
\item
Run the command \texttt{scr:list\ {[}bundle\ ID{]}}, and locate the
app's component ID. The blogs portlet entry is shown below. The last
number preceding the bundle's state is the component ID:
\begin{verbatim}
[ 100] com.liferay.blogs.web.internal.portlet.BlogsPortlet enabled
[ 196] [active]
\end{verbatim}
\item
Run the command \texttt{scr:info\ {[}component\ ID{]}} to list the
portlet's information. For example, to list the info for the blogs
portlet component, run \texttt{scr:info\ 196}. Note that the bundle
and/or component ID may be different for your instance.
\item
Search for \texttt{javax.portlet.name} in the results.
\texttt{javax.portlet.name}'s value is the portlet ID required for the
sitemap. The blogs portlet's ID is shown below:
\begin{verbatim}
javax.portlet.name = com_liferay_blogs_web_portlet_BlogsPortlet
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/resources-importer-gogo-shell.png}
\caption{Portlet IDs can be found via the Gogo Shell.}
\end{figure}
\section{Related Topics}\label{related-topics-74}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-layout-templates-and-pages-in-a-sitemap}{Defining
Layout Templates and Pages in a Sitemap}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-portlets-in-a-sitemap}{Defining
Portlets in a Sitemap}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/preparing-and-organizing-web-content-for-the-resources-importer}{Preparing
and Organizing Web Content for the Resources Importer}
\end{itemize}
\chapter{Preparing and Organizing Web Content for the Resources
Importer}\label{preparing-and-organizing-web-content-for-the-resources-importer}
You must create the resources to import with your theme. You can create
resources from scratch and/or bring in resources that you've already
created. You can leverage your HTML (basic web content), JSON
(structures), or VM or FTL (templates) files with the Resource Importer.
All web content articles require a structure and optionally a template.
Note that some articles may share the same structure and perhaps even
the same template---this is the case for all basic web content articles.
Follow these steps to prepare your web content articles:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select \emph{Edit} from the article's options menu, click the
\emph{Options} icon at the top right of the page and select \emph{View
Source}. Copy the article's raw XML into an XML file locally. Create a
folder for the article under
\texttt{resources-importer/journal/articles/} and rename it as
desired. The web content article's XML fills in the data required by
the structure. An example web content article's XML is shown below:
\begin{verbatim}
In the mid-20th century, after two of the
most violent wars in history, mankind turned
its gaze upwards to the stars. Instead of
continuing to strive against one another,
man choose instead to strive against the
limits that we had bound ourselves to. And
so the Great Space Race began.
At first the race was to reach space--get
outside the earth's atmosphere, and when
that had been reached, we shot for the moon.
After sending men to the moon, robots to
Mars, and probes beyond the reaches of our
solar system, it seemed that there was
nowhere left to go.
The Space Program aims to change that.
Beyond national boundaries, beyond what
anyone can imagine that we can do. The sky
is not the limit.
]]>
\end{verbatim}
\item
Download the web content article's structure. Open the structure and
click the \emph{Source} tab to view the structure's file. Copy and
paste its contents into a new JSON file in the
\texttt{resources-importer/journal/structures/} folder. The structure
JSON sets a wireframe, or blueprint, for an article's data. If you're
saving a basic web content article, you can copy the structure below
(replace \texttt{en\_US} with your language):
\begin{verbatim}
{
"availableLanguageIds": [
"en_US"
],
"defaultLanguageId": "en_US",
"fields": [
{
"label": {
"en_US": "Content"
},
"predefinedValue": {
"en_US": ""
},
"style": {
"en_US": ""
},
"tip": {
"en_US": ""
},
"dataType": "html",
"fieldNamespace": "ddm",
"indexType": "text",
"localizable": true,
"name": "content",
"readOnly": false,
"repeatable": false,
"required": false,
"showLabel": true,
"type": "ddm-text-html"
}
]
}
\end{verbatim}
\item
Download the structure's matching template if it has one. Open the
Actions menu for the structure and select \emph{Manage Templates} to
view the templates that use it. Create a folder for the template under
\texttt{resources-importer/journal/templates/} and copy and paste its
contents into a new FTL file. The template defines how the data should
be displayed. If you're saving a basic web content article, you can
copy the FreeMarker template below:
\begin{verbatim}
${content.getData()}
\end{verbatim}
\end{enumerate}
Repeat the steps above for each web content article you have. Note that
some web content articles may share the same structure and template; In
these cases, only one copy of the structure and template is required for
all web content articles that use them. Once your web content articles
are saved, you can place them in their proper folder structure.
\section{Related Topics}\label{related-topics-75}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-sitemap-for-the-resources-importer}{Creating
a Sitemap for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-assets-for-the-resources-importer}{Defining
Assets for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-where-to-import-your-themes-resources}{Specifying
Where to Import Your Theme's Resources}
\end{itemize}
\chapter{Defining Assets for the Resources
Importer}\label{defining-assets-for-the-resources-importer}
The \texttt{sitemap.json} file defines the pages of the site or site
template to import---along with the layout templates, portlets, and
portlet preferences of these pages. You may also want to provide details
about the assets you include with the theme. An \texttt{assets.json}
file lets you provide this information.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create the \texttt{assets.json} in your theme's
\texttt{{[}theme-name{]}/src/WEB-INF/src/resources-importer} folder.
\item
Follow the pattern below to configure your \texttt{assets.json} file.
Tags can be applied to any asset. Abstract summaries and small images
can be applied to web content articles. For example, the following
\texttt{assets.json} file specifies two tags for the
\texttt{company\_logo.png} image, one tag for the
\texttt{Custom\ Title.xml} web content article, and an abstract
summary and small image for the \texttt{Child\ Web\ Content\ 1.json}
article structure:
\begin{verbatim}
{
"assets": [
{
"name": "company_logo.png",
"tags": [
"logo",
"company"
]
},
{
"name": "Custom Title.xml",
"tags": [
"web content"
]
},
{
"abstractSummary": "This is an abstract summary.",
"name": "Child Web Content 1.json",
"smallImage": "company_logo.png"
}
]
}
\end{verbatim}
\end{enumerate}
Now you know how to configure assets for your web content!
\section{Related Topics}\label{related-topics-76}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/preparing-and-organizing-web-content-for-the-resources-importer}{Preparing
and Organizing Web Content for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-sitemap-for-the-resources-importer}{Creating
a Sitemap for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-where-to-import-your-themes-resources}{Specifying
Where to Import Your Theme's Resources}
\end{itemize}
\chapter{Specifying Where to Import Your Theme's
Resources}\label{specifying-where-to-import-your-themes-resources}
By default, resources are imported into a new site template named after
the theme, but you can also import resources into a new site or existing
sites or site templates. These options are covered below. Follow these
steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Before specifying where to import your resources, you must enable
Developer Mode in your theme. To do this, add the following property
to your theme's \texttt{liferay-plugin-package.properties} file. This
is enabled by default for themes generated with the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Liferay
Theme Generator}. Without this property enabled, you must manually
delete the sites or site templates built by the Resources Importer
each time you want to apply changes from your theme's
\texttt{src/WEB-INF/src/resources-importer} folder:
\begin{verbatim}
resources-importer-developer-mode-enabled=true
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Warning:** the `resources-importer-developer-mode-enabled=true` setting can be
dangerous since it involves *deleting* (and re-creating) the affected site or
site template. It's only intended to be used during development. **Never use it
in production.**
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Specify where to import your resources. By default, resources are
imported into a new site template named after the theme. If that's
what you want, you can skip this step. If instead you want your
resources to be imported into an existing site template, you must
specify a value for the \texttt{resources-importer-target-value}
property in your theme's \texttt{liferay-plugin-package.properties}
file:
\begin{verbatim}
#resources-importer-target-class-name
resources-importer-target-value=[site-template-name]
\end{verbatim}
Alternatively, you can import resources into an existing site.
\textbf{You must} import your resources into a site if you define both
public and private page sets in your \texttt{sitemap.json}. To import
resources into an existing site, uncomment the
\texttt{resources-importer-target-class-name} property and set it to
\texttt{com.liferay.portal.kernel.model.Group}:
\begin{verbatim}
resources-importer-target-class-name=com.liferay.portal.kernel.model.Group
resources-importer-target-value=[site-name]
\end{verbatim}
Double check the name that you're specifying. If you specify the wrong
value, you could end up deleting (and re-creating) the wrong site or
site template!
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Warning:** It's safer to import theme resources into a site template than into
an actual site. The
`resources-importer-target-class-name=com.liferay.portal.kernel.model.Group`
setting can be handy for development and testing but should be used cautiously.
Don't use this setting in a theme deployed to a production Liferay instance or
a theme submitted to Liferay Marketplace. To prepare a theme for deployment to
a production Liferay instance, use the default setting so that the resources are
imported into a site template. You can do this explicitly by setting
`resources-importer-target-class-name=com.liferay.portal.kernel.model.LayoutSetPrototype`
or implicitly by commenting out or removing the
`resources-importer-target-class-name` property.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Deploy the theme. To view your theme and its resources, log in as an
administrator, and check the Sites or Site Templates section of the
Control Panel to make sure your resources were deployed correctly.
From the Control Panel you can easily view your theme and its
resources:
\begin{itemize}
\tightlist
\item
If you imported into a site template, open its actions menu and
select \emph{View Pages} to see it.
\item
If you imported directly into a site, open its actions menu and
select \emph{Go to Public Pages} to see it.
\end{itemize}
\end{enumerate}
Great! Now you know how to specify where to import your theme's
resources.
\section{Related Topics}\label{related-topics-77}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/preparing-and-organizing-web-content-for-the-resources-importer}{Preparing
and Organizing Web Content for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-sitemap-for-the-resources-importer}{Creating
a Sitemap for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-assets-for-the-resources-importer}{Defining
Assets for the Resources Importer}
\end{itemize}
\chapter{Archiving Site Resources}\label{archiving-site-resources}
Although a \texttt{sitemap.json} is the recommended approach for
including resources with a theme, you can also export your site's data
in a LAR (Liferay Archive) file. A LAR file is version-specific; it
won't work on any version of Liferay DXP other than the one from which
it was exported. This approach does, however, require less
configuration, since it does not require a sitemap or other files. So,
if you're using the exported resources in the same version of Liferay
DXP and it's not for a theme on Liferay Marketplace, you may prefer a
LAR file.
Follow these steps to archive your site's resources:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Export the contents of a site using the site scope.
\item
Place the \texttt{archive.lar} file in your theme's
\texttt{/src/WEB-INF/src/resources-importer} folder.
\end{enumerate}
Great! Now you know how to archive your site's resources.
\section{Related Topics}\label{related-topics-78}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/preparing-and-organizing-web-content-for-the-resources-importer}{Preparing
and Organizing Web Content for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-sitemap-for-the-resources-importer}{Creating
a Sitemap for the Resources Importer}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-assets-for-the-resources-importer}{Defining
Assets for the Resources Importer}
\end{itemize}
\chapter{Troubleshooting Themes}\label{troubleshooting-themes}
These frequently asked questions and answers help you troubleshoot and
correct problems in theme development.
Click a question to view the answer.
\begin{itemize}
\tightlist
\item
\hyperref[osgi-headers-in-themes]{How can I include OSGi headers in my
theme?}
\item
\hyperref[developer-mode]{Why aren't my changes showing up after I
redeploy my theme?}
\item
\hyperref[default-theme-returned]{Why is my theme not loading? It
returns the default theme instead.}
\item
\hyperref[rtl-no-flip]{How can I prevent specific CSS rules from
transforming for RTL Languages?}
\end{itemize}
\phantomsection\label{osgi-headers-in-themes}
{How can I include OSGi headers in my theme?~{}}
\begin{verbatim}
Specify the headers you want to use in your theme's liferay-plugin-package.properties file. Any headers placed in this file are included automatically in your MANIFEST and the OSGi bundle.
For example, you can add OSGi dependencies in your theme by importing the exported package with the Import-Package header:
Import-Package:com.liferay.docs.portlet
\end{verbatim}
\phantomsection\label{developer-mode}
{Why aren't my changes showing up after I redeploy my theme?~{}}
\begin{verbatim}
By default CSS, JS, and theme template files are cached in the browser. During development, you can enable Devloper Mode to prevent your theme's files from caching.
\end{verbatim}
\phantomsection\label{default-theme-returned}
{Why is my theme not loading? It returns the default theme instead.~{}}
\begin{verbatim}
If you receive the warning "No theme found for specified theme id...", you may be referencing an outdated theme ID in your Site. Verify that the theme ID in your theme's liferay-look-and-feel.xml matches the theme ID in the warning message: "mytheme_WAR_mytheme". If the theme IDs match, there may be pages using the outdated theme instead of the Site theme. You can verify this by checking the pages manually or searching the database for layouts with values for themeId -.
\end{verbatim}
\phantomsection\label{rtl-no-flip}
{How can I prevent specific CSS rules from transforming for RTL
Languages?~{}}
\begin{verbatim}
You can prevent specific CSS rules from transforming (flipping) with the /* @noflip */ decoration. Place the decoration to the left of the CSS rule to apply it. For example, this rule gives a left margin of 20em to the body no matter if the selected language is LTR or RTL:
/* @noflip */ body {
margin-left: 20em;
}
You can also use the .rtl CSS selector for rules that exclusively apply to RTL languages.
\end{verbatim}
\chapter{Layout Templates}\label{layout-templates}
Layout templates dictate where content and apps can be placed on a page.
There are several default layout templates for organizing content on
your pages:
\begin{figure}
\centering
\includegraphics{./images/page-select-layout.png}
\caption{There are many default layout templates to choose from.}
\end{figure}
If the default layouts don't work for your site, you can create your own
layout template by following the articles listed below:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Create
the layout template}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-layout-template-thumbnail-previews}{Create
the layout template thumbnail preview}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/including-layout-templates-with-a-theme}{Bundle
the layout template with a theme}
\end{itemize}
Layout Templates specify a number of rows and columns for the page. The
rows and columns dictate where apps (and fragments) can be placed on the
page. Layout templates are written in
\href{https://freemarker.apache.org/}{FreeMarker}. An example row's HTML
markup is shown below:
\begin{verbatim}
${processor.processColumn("column-1",
"portlet-column-content portlet-column-content-first")}
${processor.processColumn("column-2",
"portlet-column-content portlet-column-content-last")}
\end{verbatim}
Columns use the
\href{https://getbootstrap.com/docs/4.0/layout/grid/}{Bootstrap grid
system}. Every row consists of twelve sections, so columns can range in
size from \texttt{1} to \texttt{12}. Sizes are indicated with the number
that follows the \texttt{col-{[}breakpoint{]}} class prefix
(e.g.~\texttt{col-md-6}). These specify two things: the percentage-based
width of the element and the media query breakpoint (\texttt{xs},
\texttt{sm}, \texttt{md}, or \texttt{lg}), which specifies when the
element expands to 100\% width. For example, \texttt{col-md-6} indicates
\texttt{6/12} width, or \texttt{50\%}. These classes can also be mixed
to achieve more advanced layouts, as shown above. In the example, medium
sized viewports display \texttt{column-1} at 33.33\% width and
\texttt{column-2} at 66.66\% width, while both \texttt{column-1} and
\texttt{column-2} are 50\% width on small sized view ports.
The processor (\texttt{\$\{processor.processColumn()\}}) processes each
column's content, taking two arguments: the column's \texttt{id}, and
the classes \texttt{portlet-column-content} and
\texttt{portlet-column-content-{[}case{]}} (if applicable), where
\texttt{{[}case{]}} refers to the \texttt{first}, \texttt{last}, or
\texttt{only} column in the row.
\chapter{Creating Custom Layout Template Thumbnail
Previews}\label{creating-custom-layout-template-thumbnail-previews}
To showcase your layout template properly, you must provide a thumbnail
preview for it. Without this, no one will know the design of the layout.
Follow these steps to provide a thumbnail preview for your layout
template:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the layout template's \texttt{docroot/} folder. If you
bundled the layout template with a theme created with the Liferay
Theme Generator, the thumbnail is located in your theme's
\texttt{src/layouttpl/custom/my-layoutttpl} folder.
\item
Create a custom thumbnail PNG inside the folder specified in step 1
with the same name as the layout template that is 120 px x 120 px .
Delete the temporary thumbnail PNG file if it exist.
\begin{figure}
\centering
\includegraphics{./images/porygon_50_50_width_limited.png}
\caption{A thumbnail preview displays the layout's design to the
user.}
\end{figure}
\item
Deploy your layout template to your app server to use it. If your
layout template is
\href{/docs/7-2/frameworks/-/knowledge_base/f/including-layout-templates-with-a-theme}{bundled
with a theme}, it deploys when the theme is deployed. Now you know how
to create a custom thumbnail preview for your Liferay DXP layout
templates!
\end{enumerate}
\section{Related topics}\label{related-topics-79}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Layout
Templates with the Liferay Theme Generator}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/including-layout-templates-with-a-theme}{Bundling
Layout Templates with a Theme}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes}
\end{itemize}
\chapter{Including Layout Templates with a
Theme}\label{including-layout-templates-with-a-theme}
Although you can deploy a layout template by itself, you can also bundle
it with a theme. If you generated a layout template with the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Layouts
sub-generator} from inside a generated theme project, the layout
template is bundled with the theme automatically. If, however, you
generated a layout template and want to bundle it with a theme
afterwards, follow these steps to include the layout template with a
theme:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Copy the layout template's \texttt{liferay-layout-templates.xml} file
to the theme's \texttt{src/WEB-INF/} folder.
\item
Create a \texttt{layouttpl/custom/my-layouttpl/} folder inside the
theme's \texttt{src/} folder.
\item
Copy the layout template's FreeMarker (.ftl) file, and
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-layout-template-thumbnail-previews}{thumbnail
preview} (.png) if it exist, over to the
\texttt{layouttpl/custom/my-layouttpl/} folder.
\item
Copy the theme's \texttt{liferay-theme.json} file into the
\texttt{src/layouttpl/custom/my-layouttpl/} folder and rename it
\texttt{liferay-plugin.json}.
\item
Open \texttt{liferay-plugin.json}, rename the \texttt{LiferayTheme}
entry \texttt{LiferayPlugin}, and replace the \texttt{pluginName}
entry's value with the name of the layout template. Below is an
example configuration:
\end{enumerate}
\begin{verbatim}
{
"LiferayPlugin": {
"deploymentStrategy": "LocalAppServer",
"appServerPath": "C:\\Users\\liferay\\opt\\Liferay\\bundles\\liferay-ce-portal-tomcat-7.2.0\\tomcat-9.0.10",
"deployPath": "C:\\Users\\liferay\\opt\\Liferay\\bundles\\liferay-ce-portal-tomcat-7.2.0\\tomcat-9.0.10\\deploy",
"url": "http://localhost:8080",
"appServerPathPlugin": "C:\\Users\\liferay\\opt\\Liferay\\bundles\\liferay-ce-portal-tomcat-7.2.0\\tomcat-9.0.10\\webapps\\my-layouttpl",
"deployed": false,
"pluginName": "my-layouttpl"
}
}
\end{verbatim}
Now you know how to include layout templates with your Liferay DXP
themes!
\section{Related topics}\label{related-topics-80}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Layout
Templates with the Liferay Theme Generator}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-layout-template-thumbnail-previews}{Creating
Custom Layout Template Thumbnail Previews}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes}
\end{itemize}
\chapter{Creating and Bundling JavaScript Widgets with JavaScript
Tooling}\label{creating-and-bundling-javascript-widgets-with-javascript-tooling}
Portlets are a Java standard, so you must have a knowledge and
understanding of how Java works to write one. This can be quite the
hurdle for front-end developers who want to use JavaScript frameworks in
their widgets. Thanks to the Liferay JS Generator and
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-npm-bundler}{liferay-npm-bundler},
developers can easily create and develop JavaScript widgets in Liferay
DXP using pure JavaScript tooling. The Liferay JS Generator generates
JavaScript widgets for Liferay DXP. It is just one of Liferay JS
Toolkit's
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages}{tools}.
\noindent\hrulefill
\textbf{Note:} To use the Liferay JS Generator, you must have the
Liferay JS Portlet Extender activated in your Liferay DXP instance. It's
activated by default. You can confirm this by opening the Control Menu,
navigating to the \emph{App Manager}, and searching for
\texttt{com.liferay.frontend.js.portlet.extender}.
\noindent\hrulefill
\begin{figure}
\centering
\includegraphics{./images/extender-lifecycle.png}
\caption{The JS Portlet Extender lets you use pure JavaScript tooling to
write widgets.}
\end{figure}
\noindent\hrulefill
\textbf{Note:} JavaScript Server-Side-Rendering is not supported
out-of-the-box. To use JS frameworks for site rendering, you
\textbf{must} set up your server-side (or search-crawler) rendering
generation to support them.
\noindent\hrulefill
Once you've
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-js-generator-and-generating-a-bundle}{installed
the Liferay JS Generator and generated a widget}, you can configure
instance settings, system settings, and even provide localization for
your widget. This section explains how to configure these options for
your generated JS widget.
\chapter{Configuring System Settings and Instance Settings for Your
JavaScript
Widget}\label{configuring-system-settings-and-instance-settings-for-your-javascript-widget}
As of v1.1.0 of the JS Portlet Extender, you can define configuration
options for your widget. These options are passed to the widget's
JavaScript entry point as the \texttt{configuration} parameter. See the
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-the-js-portlet-extender-configuration\#main-entry-point}{main
entry point's reference} for more information on the entry point. Follow
these steps to set system and/or portlet instance settings for your
widget:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Add a \texttt{/features} folder in your project's root folder if it
doesn't already exist.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** This location can be overridden with the
`create-jar.features.configuration` option in your project's `.npmbundlerrc`
file. See [OSGi bundle configuration options](/docs/7-2/reference/-/knowledge_base/r/understanding-the-npmbundlerrcs-structure#osgi-bundle-creation-options)
for all the available options for the bundle.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Create a \texttt{configuration.json} file in the \texttt{/features}
folder and follow the pattern below. See the
\href{/docs/7-2/reference/-/knowledge_base/r/configuration-json-available-options}{Configuration
JSON} reference for an explanation of each of the available options:
\begin{verbatim}
{
"system": {
"category": "{category identifier}",
"name": "{name of configuration}",
"fields": {
"{field id 1}": {
"type": "{field type}",
"name": "{field name}",
"description": "{field description}",
"default": "{default value}",
"options": {
"{option id 1}": "{option name 1}",
"{option id 2}": "{option name 2}",
"{option id n}": "{option name n}"
}
},
"{field id 2}": {},
"{field id n}": {}
}
},
"portletInstance": {
"name": "{name of configuration}",
"fields": {
"{field id 1}": {
"type": "{field type}",
"name": "{field name}",
"description": "{field description}",
"default": "{default value}",
"options": {
"{option id 1}": "{option name 1}",
"{option id 2}": "{option name 2}",
"{option id n}": "{option name n}"
}
},
"{field id 2}": {},
"{field id n}": {}
}
}
}
\end{verbatim}
\item
Access a system setting's value or a portlet instance setting's value
with the syntax \texttt{configuration.system} or
\texttt{configuration.portletInstance} respectively. For instance, to
retrieve the \texttt{\{field\ id\ 1\}} system settings value, you
would use \texttt{configuration.system.\{field\ id\ 1\}}. Note that
all fields are passed as strings no matter what type they declare in
their descriptor.
\end{enumerate}
Awesome! Now you know how to configure system settings and portlet
instance settings for your widget.
\section{Related Topics}\label{related-topics-81}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-widget}{Localizing
Your Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-translation-features-in-your-widget}{Using
Translation Features in Your JavaScript Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-portlet-properties-for-your-widget}{Setting
Portlet Properties for Your JavaScript Widget}
\end{itemize}
\chapter{Localizing Your Widget}\label{localizing-your-widget}
Follow the steps below to learn how to localize your widget:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
If you chose not to use localization when you generated the bundle,
follow this step to enable it in your bundle. Create a
\texttt{/features/localization} folder in your project and add a
\texttt{Language.properties} file to it. Add a
\texttt{create-jar.features.localization} key to your
\texttt{.npmbuildrc} file that points to the
\texttt{Language.properties} file. An example configuration is shown
below:
\begin{verbatim}
{
"create-jar": {
"output-dir": "dist",
"features": {
"js-extender": true,
"web-context": "/my-test-js-widget",
"localization": "features/localization/Language",
"settings": "features/settings.json"
}
},
...
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** The default file path is shown above. You can update this value,
if you want to place your `Language.properties` file in a different
location.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Configure the \texttt{Language.properties} file and provide the
localized property files
(e.g.~\texttt{Language\_{[}locale{]}.properties}) with the
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{language
keys} for each
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Languages\%20and\%20Time\%20Zones}{available
translation}. The \emph{JavaScript based widget} configuration is
shown below:
\begin{verbatim}
javax.portlet.title.my_js_portlet_project=My JS Widget Project
portlet-namespace=Portlet Namespace
context-path=Context Path
portlet-element-id=Portlet Element Id
configuration=Configuration
fruit=Favourite fruit
fruit-help=Choose the fruit you like the most
an-orange=An orange
a-pear=A pear
an-apple=An apple
\end{verbatim}
\item
Retrieve a language key's localized value in JavaScript with the
\texttt{Liferay.Language.get(\textquotesingle{}key\textquotesingle{})}
method.
\end{enumerate}
Great! Now you know how to localize your widget!
\section{Related Topics}\label{related-topics-82}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-system-settings-and-instance-settings-for-your-js-widget}{Configuring
System Settings and Instance Settings for Your JavaScript Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-translation-features-in-your-widget}{Using
Translation Features in Your JavaScript Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-portlet-properties-for-your-widget}{Setting
Portlet Properties for Your JavaScript Widget}
\end{itemize}
\chapter{Using Translation Features in Your
Widget}\label{using-translation-features-in-your-widget}
By default, the Liferay JS Generator creates an empty configuration for
translation. The translate script instructs the user how to add new
supported locales or configure the credentials when it's run. The
translate target reads the supported locales you have defined in the
\texttt{supportedLocales} key of your \texttt{.npmbuildrc} file and
checks your \texttt{*language.properties} files to make sure they match.
\noindent\hrulefill
\textbf{Note:} To use the translation features, you must have a
Microsoft Translator key. Provide your credentials through either the
\texttt{translatorTextKey} variable in your \texttt{.npmbuildrc} file,
or provide them in the \texttt{TRANSLATOR\_TEXT\_KEY} environment
variable.
\noindent\hrulefill
Follow these steps to add a new supported locale and automatically
create a language properties file for it with translations:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the locale to the \texttt{supportedLocales} array in your
\texttt{.npmbuildrc} file.
\item
Run the translate target with the command below:
\begin{verbatim}
npm run translate
\end{verbatim}
\item
The translate target automatically creates a language properties file
for each new \textbf{supported} locale with translations for your
language keys. It also warns about locales that are not supported, but
have a \texttt{*language.properties} file.
\end{enumerate}
Great! Now you know how to use the Liferay JS Generator's translation
features in your app.
\section{Related Topics}\label{related-topics-83}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-system-settings-and-instance-settings-for-your-js-widget}{Configuring
System Settings and Instance Settings for Your JavaScript Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-widget}{Localizing
Your Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-portlet-properties-for-your-widget}{Setting
Portlet Properties for Your JavaScript Widget}
\end{itemize}
\chapter{Configuring Portlet Properties for Your
Widget}\label{configuring-portlet-properties-for-your-widget}
Follow these steps to configure your widget's properties:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your generated JavaScript widget's \texttt{package.json} file.
\item
Set the properties under the \texttt{portlet} entry. Note that these
are the same properties you would define in the Java
\texttt{@Component} annotation of a portlet, as defined in the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-portlet-app_7_2_0.dtd.html}{liferay-portlet-app\_7\_2\_0.dtd}.
An example configuration is shown below:
\begin{verbatim}
"portlet": {
"com.liferay.portlet.display-category": "category.sample",
"com.liferay.portlet.header-portlet-css": "/css/styles.css",
"com.liferay.portlet.instanceable": true,
"javax.portlet.name": "my_js_portlet_project",
"javax.portlet.security-role-ref": "power-user,user",
"javax.portlet.resource-bundle": "content.Language"
},
\end{verbatim}
\item
Deploy your bundle to apply the changes.
\end{enumerate}
Great! Now you know how to configure your JavaScript widget's
properties.
\section{Related Topics}\label{related-topics-84}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-system-settings-and-instance-settings-for-your-js-widget}{Configuring
System Settings and Instance Settings for Your JavaScript Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-widget}{Localizing
Your Widget}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-translation-features-in-your-widget}{Using
Translation Features in Your JavaScript Widget}
\end{itemize}
\chapter{JavaScript Module Loaders}\label{javascript-module-loaders}
A JavaScript module encapsulates code into a useful unit that exports
its capability/value. This makes it easier to see the broader scope,
easier to find what you're looking for, and keeps related code close
together. A normal web page usually loads JavaScript files via HTML
\texttt{script} tags. That's fine for small websites, but when
developing large scale web applications, a more robust organization and
loader is needed. A module loader lets an application load dependencies
easily by specifying a string that identifies the JavaScript module's
name.
This section shows how to load JavaScript modules in Liferay DXP.
\chapter{Loading AMD Modules in
Liferay}\label{loading-amd-modules-in-liferay}
Modularized JavaScript code is a specification for the JavaScript
language called Asynchronous Module Definition, or AMD. The
\href{https://github.com/liferay/liferay-amd-loader\#amd-module-loader}{Liferay
AMD Module Loader} is the native loader that you can use to load your
AMD modules. The steps below cover how to use the Liferay AMD Module
Loader.
\noindent\hrulefill
\textbf{Note:} While you can manually configure the AMD Loader, we
recommend that you use the
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-npm-in-your-portlets}{liferay-npm-bundler}
instead.
\noindent\hrulefill
Follow these steps to prepare your module for the Liferay AMD Module
Loader:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Wrap your AMD module code with the \texttt{Liferay.Loader.define()}
method, such as the one shown below:
\begin{verbatim}
Liferay.Loader.define('my-dialog', ['my-node', 'my-plugin-base'],
function(myNode, myPluginBase) {
return {
log: function(text) {
console.log('module my-dialog: ' + text);
}
};
});
\end{verbatim}
\item
You can modify the configuration to load the module when another
module is triggered or when a condition is met. The configuration
below specifies that this module should be loaded if another module
requests the \texttt{my-test} module:
\begin{verbatim}
Liferay.Loader.define('my-dialog', ['my-node', 'my-plugin-base'],
function(myNode, myPluginBase) {
return {
log: function(text) {
console.log('module my-dialog: ' + text);
}
};
}, {
condition: {
trigger: 'my-test',
test: function() {
var el = document.createElement('input');
return ('placeholder' in el);
}
},
path: 'my-dialog.js'
});
\end{verbatim}
The Liferay AMD Loader uses the definition, along with the listed
dependencies, as well as any other configurations specified, to create
a \texttt{config.json} file. This configuration object tells the
loader which modules are available, where they are located, and what
dependencies they require. Below is an example of a generated
\texttt{config.json} file:
\begin{verbatim}
{
"frontend-js-web@1.0.0/html/js/parser": {
"dependencies": []
},
"frontend-js-web@1.0.0/html/js/list-display": {
"dependencies": ["exports"]
},
"frontend-js-web@1.0.0/html/js/autocomplete": {
"dependencies": ["exports", "./parser", "./list-display"]
}
}
\end{verbatim}
\item
Load your module in your scripts. Pass the module name to the
\texttt{Liferay.Loader.require} method. The example below loads a
module called \texttt{my-dialog}:
\begin{verbatim}
Liferay.Loader.require('my-dialog', function(myDialog) {
// your code here
}, function(error) {
console.error(error);
});
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** By default, the AMD Loader times out in seven seconds. You can
configure this value through System Settings. Open the Control Panel and
navigate to *Configuration* → *System Settings* → *PLATFORM* →
*Infrastructure*, and select *JavaScript Loader*. Set the *Module Definition
Timeout* configuration to the time you want and click *Save*.
\end{verbatim}
\noindent\hrulefill
\section{Related Topics}\label{related-topics-85}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-modules-with-aui-script}{Loading
Modules with AUI Script}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-npm-in-your-portlets}{Using
npm in Your Portlets}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-modules-with-aui-script}{Loading
Modules with AUI Script}
\end{itemize}
\chapter{Using External JavaScript
Libraries}\label{using-external-javascript-libraries}
You can use external JavaScript libraries in your portlets (i.e.,
anything but Metal.js or jQuery, which are included by default). If
you're the owner or hosting the external library, there are a few more
requirements to load them with the JavaScript Loaders.
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
If you're the owner of the library, you should make sure that it
supports \href{https://github.com/umdjs/umd}{UMD} (Universal Module
Definition). You can configure your code to support UMD with the
template shown below:
\begin{verbatim}
// Assuming your "module" will be exported as "mylibrary"
(function (root, factory) {
if (typeof Liferay.Loader.define === 'function' && Liferay.Loader.define.amd) {
// AMD. Register as a "named" module.
Liferay.Loader.define('mylibrary', [], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.mylibrary = factory();
}
}(this, function () {
// Your library code goes here
return {};
}));
\end{verbatim}
\item
If you're hosting the library (and not loading it from a CDN), you
must hide the Liferay AMD Loader to use your Library. Open the Control
Panel, navigate to \emph{Configuration} → \emph{System Settings}.
\item
Click \emph{JavaScript Loader} under \emph{Platform} →
\emph{Infrastructure}.
\item
Uncheck the \texttt{expose\ global} option.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} Once this option is unchecked, you can no longer use the
\texttt{Liferay.Loader.define} or \texttt{Liferay.Loader.require}
functions in your app. Also, if you're using third party libraries that
are AMD compatible, they could stop working after unchecking this option
because they usually use global functions like \texttt{require()} or
\texttt{define()}.
\noindent\hrulefill
Great! Now you know how to adapt external libraries for Liferay's
JavaScript Loaders.
\section{Related Topics}\label{related-topics-86}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-amd-modules-in-liferay}{Liferay
AMD Module Loader}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-esplus-modules-in-your-portlet}{Using
ES2015+ Modules in Your Portlet}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-modules-with-aui-script}{Loading
Modules with AUI Script}
\end{itemize}
\chapter{Loading Modules with AUI
Script}\label{loading-modules-with-aui-script}
The \texttt{aui:script} tag is a JSP tag that loads JavaScript on the
page, while ensuring that certain resources are loaded before executing.
\noindent\hrulefill
\textbf{Note:} AUI is deprecated and no longer in active development in
7.0, but all the tags will remain fully functional in Liferay DXP 7.2.
Eventually, these tags will be replaced with
\href{https://clayui.com/}{Clay} tag counterparts.
\noindent\hrulefill
The \texttt{aui:script} tag supports the following options:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Option
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{require} & Requires an AMD module to load with the
\href{https://github.com/liferay/liferay-amd-loader\#amd-module-loader}{Liferay
AMD Module Loader}. \\
\texttt{use} & Uses an AlloyUI/YUI module that is loaded via the YUI
loader. \\
\texttt{position} & The position the script tag is put on the page.
Possible options are \texttt{inline} or \texttt{auto}. \\
\texttt{sandbox} & Whether to wrap the script tag in an anonymous
function. If set to \texttt{true}, in addition to the wrapping,
\texttt{\$} and \texttt{\_} are defined for jQuery and underscore. \\
\end{longtable}
\noindent\hrulefill
This section covers how to load ES2015, Metal.js, and AUI modules with
the AUI script tag.
\chapter{Loading AlloyUI Modules with AUI
Script}\label{loading-alloyui-modules-with-aui-script}
Follow these steps to load modules with
\texttt{\textless{}aui:script\textgreater{}}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following declaration to your portlet's JSP:
\begin{verbatim}
<%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %>
\end{verbatim}
\item
Add the \texttt{\textless{}aui:script\textgreater{}} tag and use the
\texttt{use} attribute to load AlloyUI/YUI modules:
\begin{verbatim}
A.one('#someNodeId').on(
'click',
function(event) {
alert('Thank you for clicking.')
}
);
\end{verbatim}
\end{enumerate}
This loads the \texttt{aui-base} AlloyUI component and makes it
available to the code inside the \texttt{aui:script}.
In the browser, the \texttt{aui:script} translates to the full
JavaScript shown below:
\begin{verbatim}
\end{verbatim}
Wonderful! Now you know how to load AUI/YUI modules in AUI scripts.
\section{Related Topics}\label{related-topics-87}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-external-javascript-libraries}{Using
External JavaScript Libraries}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-amd-modules-in-liferay}{Loading
AMD Modules}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-es2015-and-metal-modules-with-aui-script}{Loading
ES2015 and Metal.js Modules with AUI Script}
\end{itemize}
\chapter{Loading ES2015 and Metal.js Modules with AUI
Script}\label{loading-es2015-and-metal.js-modules-with-aui-script}
Follow these steps to load your ES2015 and Metal.js modules with
\texttt{\textless{}aui:script\textgreater{}}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following declaration to your portlet's JSP:
\begin{verbatim}
<%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %>
\end{verbatim}
\item
Add the \texttt{\textless{}aui:script\textgreater{}} tag and use the
\texttt{require} attribute to load ES2015 and Metal.js modules:
\begin{verbatim}
new metalClipboardSrcClipboard.default();
\end{verbatim}
\end{enumerate}
alternatively, you can specify a variable for your module by adding
\texttt{as\ variableName} after the module name, as shown in the example
below:
\begin{verbatim}
new myModule.default();
\end{verbatim}
This resolves the dependencies of the registered module and loads them
in order until all of them are satisfied and the requested module can be
safely executed.
In the browser, the \texttt{aui:script} translates to the full
JavaScript shown below:
\begin{verbatim}
\end{verbatim}
Great! Now you know how to load ES2015 and Metal.js modules in AUI
scripts.
\section{Related Topics}\label{related-topics-88}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-external-javascript-libraries}{Using
External JavaScript Libraries}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-amd-modules-in-liferay}{Loading
AMD Modules}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-aui-es2015-and-metal-modules-with-aui-script}{Loading
AUI, ES2015, and Metal.js Modules Together with AUI Script}
\end{itemize}
\chapter{Loading AUI, ES2015, and Metal.js Modules Together with AUI
Script}\label{loading-aui-es2015-and-metal.js-modules-together-with-aui-script}
You may want to load an AUI module along with an ES2015 module or
Metal.js module in an \texttt{aui:script}. The \texttt{aui:script} tag
doesn't support both the \texttt{require} and \texttt{use} attributes in
the same configuration. Not to worry though. You can use the
\texttt{aui:script}'s \texttt{require} attribute to load the ES2015 and
Metal.js modules, while loading the AUI module(s) with the
\texttt{AUI().use()} function within the script.
Follow these steps to load your ES2015, Metal.js, and AUI modules
together with \texttt{\textless{}aui:script\textgreater{}}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following declaration to your portlet's JSP:
\begin{verbatim}
<%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %>
\end{verbatim}
\item
Add the \texttt{\textless{}aui:script\textgreater{}} tag and use the
\texttt{require} attribute to load ES2015 and Metal.js modules, while
using the \texttt{AUI().use()} function to load AUI modules, as shown
in the example below:
\begin{verbatim}
AUI().use(
'liferay-aui-module',
function(A) {
let var = pathToMetalModule.default;
}
);
\end{verbatim}
\end{enumerate}
Great! Now you know how to load all your modules with
\texttt{aui:script}.
\section{Related Topics}\label{related-topics-89}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-external-javascript-libraries}{Using
External JavaScript Libraries}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-amd-modules-in-liferay}{Loading
AMD Modules}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-es2015-and-metal-modules-with-aui-script}{Loading
ES2015 and Metal.js Modules with AUI Script}
\end{itemize}
\chapter{Loading Bundled npm Modules in Your
Portlets}\label{loading-bundled-npm-modules-in-your-portlets}
Once you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-javascript-in-your-portlets}{exposed
your modules}, you can use them in your portlet via the
\texttt{aui:script} tag's \texttt{require} attribute. You can load the
npm module in your portlet using the \texttt{npmResolvedPackageName}
variable, which is available by default since 7.0. You can then create
an alias to reference it in your portlet.
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Provide a \texttt{Web-ContextPath} in your bundle's \texttt{bnd.bnd}
file:
\begin{verbatim}
Web-ContextPath: /my-module-web
\end{verbatim}
\item
Make sure the
\texttt{\textless{}liferay-frontend:defineObjects\ /\textgreater{}}
tag is included in your portlet's \texttt{init.jsp}. This makes the
\texttt{npmResolvedPackageName} variable available, setting it to your
project module's resolved name. For instance, if your module is called
\texttt{my-module} and is at version \texttt{2.3.0}, the implicit
variable \texttt{npmResolvedPackageName} is set to
\texttt{my-module@2.3.0}. This lets you prefix any JS module
\texttt{require} or soy component rendering with this variable.
\item
Use the \texttt{npmResolvedPackageName} variable along with the
relative path to your JavaScript module file to create an alias in the
\texttt{\textless{}aui:script\textgreater{}}'s \texttt{require}
attribute. An example configuration is shown below:
\begin{verbatim}
\end{verbatim}
\item
Use the alias inside the \texttt{aui:script} to refer to your module:
\begin{verbatim}
myModule.default();
\end{verbatim}
\end{enumerate}
Now you know how to use an npm module's package!
\section{Related Topics}\label{related-topics-90}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/obtaining-dependency-npm-package-descriptors}{Obtaining
an OSGi bundle's Dependency npm Package Descriptors}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-npm-bundler}{liferay-npm-bundler}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/how-the-liferay-npm-bundler-publishes-npm-packages}{How
liferay-npm-bundler Publishes npm Packages}
\end{itemize}
\chapter{The Info Framework}\label{the-info-framework}
7.0 introduces the Info Framework to provide a greater degree of
extensibility for the most common needs of retrieving, processing and
displaying any type of information. A key aspect of the Info Framework
is that it makes no assumptions about the source of the data or how it
is represented in memory (like which Java class the information is
from). It can work with information stored in the database, created
through some process in memory or retrieved from an external source.
In 7.0, the Info Framework still has limited functionality, but it sets
the foundation for obtaining and displaying information from external
systems or custom data models in Liferay. It also provides flexibility
in customizing how any piece of information is displayed.
The Info Framework is lightweight. By design, it does not require all
the information to implement any specific interface. This means that you
can use it with any existing Java class, even if you don't have access
to modify it. It comprises a collection of loosely coupled
micro-frameworks, so that developers can choose which features to use
and ignore the others. This lowers the learning curve and minimizes work
for developers.
\noindent\hrulefill
\textbf{Note:} Liferay veterans may notice similarities between the
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset
Framework} and Info Frameworks. The Info Framework can be considered a
generalization of the Asset Framework and its design has considered lots
of lessons learned from the Asset Framework. In particular, the Info
Framework provides many similar characteristics (such as rendering of
information) but with fewer requirements (such as having an
\texttt{AssetEntry} in the database). We have also made sure the two
frameworks play well together so that when the Asset Framework is used,
for rendering an asset, the renderer is also available through the Info
framework. The Info framework is not meant to be a full replacement for
the Asset Framework, but if the Asset Framework seems too complex for
your needs, the Info Framework might be a better fit for you.
\noindent\hrulefill
\section{Using the Info Framework}\label{using-the-info-framework}
The Info Framework helps generalize information handling. Custom
applications can use it to make them more generic and extensible.
Some of the out-of-the-box Liferay features use it to achieve that same
goal. In particular, Liferay DXP 7.2 uses it in two ways:
\begin{itemize}
\item
The Asset Publisher can display Assets from Information Lists defined
by the Info Framework.
\item
Display Pages, which previously only worked for an
\texttt{AssetEntry}, can now leverage the \texttt{InfoFramework} to
create display pages for any type of information that can be
represented by a Java class. Developers can add support for display
pages for various entities like Orders, Categories, and Events that
are not Assets.
\end{itemize}
There are currently two tools provided by the Info Framework:
Information Item Renderers and Information List Providers. You can
create an Information List Provider to obtain a list of information
items from a source, or create an Information Item Renderer to provide a
custom renderer for any type of information. These two features can be
used together or separately.
\section{List Providers}\label{list-providers}
Information List Providers obtain a list of information items from a
source. To do this, a developer must implement the
\texttt{InfoListProvider} interface and provide the necessary logic for
retrieving the information from its source. By providing an
implementation of the \texttt{InfoListProvider} interface, developers
can provide programmatic retrieval of information of any type, as long
as it can be represented through a Java class.
\texttt{InfoListProvider} has four methods to implement:
\texttt{getLabel()} provides the label that is displayed for this
provider in the UI of applications like the Asset Publisher.
\texttt{getInfoList()} provides the information list. This method has
two variants: a plain list or a list with pagination and sorting.
\texttt{getInfoListCount()} provides total number of items. This is
needed for the paginated variant of \texttt{getInfoList}.
For an example of how to create Information List providers, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-information-list-provider}{Creating
Information List Providers}.
\section{Item Renderers}\label{item-renderers}
Developers can create custom renderers for any type of information. To
do this, a developer must provide an implementation of the
\texttt{InfoItemRenderer} interface to provide programmatic rendering of
information. It can be any kind of information as long as it can be
represented through a Java class. You can create multiple renderers for
a single type of information.
Internally, Liferay's Display Pages use this from the Content component.
When it is added to a display page template, this component renders
whatever piece of information is shown through that template (whether it
is Content in the strict sense or some other entity type). It is
rendered by the first \texttt{InfoItemRenderer} class registered that
entity. Information Item Renderers will be leveraged further in future
Liferay versions.
To create an Information Item Renderer you must create a class that
implements \texttt{InfoItemRenderer} and registers it as a component.
Inside that class, you need a \texttt{render()} method that contains
your logic. To learn about Information Item Renderers, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/custom-rendering-of-information-with-infoitemrenderer}{Creating
Information Item Renderers}.
\chapter{Creating an Information List
Provider}\label{creating-an-information-list-provider}
To demonstrate Information List Providers, follow the instructions below
to implement an \texttt{InfoListProvider} for the most viewed asset
entries. In this case the list shows a list of \texttt{AssetEntry}
instances. Since they already have their own renderer, they can appear
in the Asset Publisher with no additional changes. If you create a
provider for a custom class, you must also render it.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{module}
named asset-entry-info-list-provider.
\item
Create a package inside the module named
\texttt{com.liferay.docs.info.provider}.
\item
Inside the package, create a class named
\texttt{AssetEntryInfoListProvider} that implements
\texttt{InfoListProvider} and registers it as a component:
\begin{verbatim}
@Component(service = InfoListProvider.class)
public class AssetEntryInfoListProvider implements InfoListProvider {
}
\end{verbatim}
\item
Next, add the necessary \texttt{@Reference} that you need for the
logic of retrieving assets to the bottom of the class.
\begin{verbatim}
@Reference
AssetEntryLocalService _assetEntryLocalService;
\end{verbatim}
\item
Then implement \texttt{getInfoList} which returns just the list.
\begin{verbatim}
@Override
public List getInfoList(
InfoListProviderContext infoListProviderContext) {
return _assetEntryLocalService.getTopViewedEntries(
new String[0], false, 0, 20);
}
\end{verbatim}
Descending order and a maximum of 20 items to return is hardcoded.
\item
Now implement the second method, which provides greater control over
how items are returned to the provider.
\begin{verbatim}
@Override
public List getInfoList(
InfoListProviderContext infoListProviderContext, Pagination pagination,
Sort sort) {
return _assetEntryLocalService.getTopViewedEntries(
new String[0], !sort.isReverse(), pagination.getStart(),
pagination.getEnd());
}
\end{verbatim}
\item
Provide a method to get a full count of info list items.
\begin{verbatim}
@Override
public int getInfoListCount(
InfoListProviderContext infoListProviderContext) {
Company company = infoListProviderContext.getCompany();
return _assetEntryLocalService.getCompanyEntriesCount(
company.getCompanyId());
}
\end{verbatim}
\item
Finally, add a method that provides a display label for the list.
\begin{verbatim}
@Override
public String getLabel(Locale locale) {
return "Most Viewed Content";
}
\end{verbatim}
\end{enumerate}
The completed class should look like this:
\begin{verbatim}
@Component(service = InfoListProvider.class)
public class AssetEntryInfoListProvider implements InfoListProvider {
@Override
public List getInfoList(
InfoListProviderContext infoListProviderContext) {
return _assetEntryLocalService.getTopViewedEntries(
new String[0], false, 0, 20);
}
@Override
public List getInfoList(
InfoListProviderContext infoListProviderContext, Pagination pagination,
Sort sort) {
return _assetEntryLocalService.getTopViewedEntries(
new String[0], !sort.isReverse(), pagination.getStart(),
pagination.getEnd());
}
@Override
public int getInfoListCount(
InfoListProviderContext infoListProviderContext) {
Company company = infoListProviderContext.getCompany();
return _assetEntryLocalService.getCompanyEntriesCount(
company.getCompanyId());
}
@Override
public String getLabel(Locale locale) {
return "Most Viewed Content";
}
@Reference
AssetEntryLocalService _assetEntryLocalService;
}
\end{verbatim}
This class is now ready to go! If you deploy it, it shows the ``Most
Viewed Content'' in any Asset Publisher.
\section{Next steps}\label{next-steps}
This example is pretty simplistic and probably not useful in real world
cases. To begin with, you may want to scope the search to the current
site. You can also add more advanced filter criteria or provide a
configuration for the provider using Liferay's configuration framework.
As mentioned, it is also possible to implement providers for custom
types. The following code shows a partial example of a provider for a
custom \texttt{MyOrder} class:
\begin{verbatim}
@Component(service = InfoListProvider.class)
public class MyOrderProvider implements InfoListProvider {
@Override
public List getInfoList(
InfoListProviderContext infoListProviderContext, Pagination pagination,
Sort sort) {
return _myOrderLocalService.getOrders(
[...], !sort.isReverse(), pagination.getStart(),
pagination.getEnd());
}
[..]
@Reference
MyOrderLocalService _myOrderLocalService;
\end{verbatim}
\chapter{\texorpdfstring{Custom rendering of information with
\texttt{InfoItemRenderer}}{Custom rendering of information with InfoItemRenderer}}\label{custom-rendering-of-information-with-infoitemrenderer}
To demonstrate the \texttt{InfoItemRenderer}, implement a class that can
render information provided through a custom class called
\texttt{MyOrder}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{module}
named \texttt{my-order}.
\item
In \texttt{my-order}, create a package named
\texttt{com.liferay.docs.info.myorder}
\item
In the package, create a class that implements
\texttt{InfoItemRenderer} and register it as a component.
\begin{verbatim}
@Component(service = InfoItemRenderer.class)
public class MyOrderRenderer implements InfoItemRenderer {
@Override
public void render(
MyOrder myOrder, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
}
}
\end{verbatim}
\item
Next you must add the logic for the \texttt{render()} method.
\begin{verbatim}
@Override
public void render(
MyOrder myOrder, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
StringBundler sb = new StringBundler(3);
sb.append("");
sb.append("By: " + myOrder.getBy());
sb.append(" When: " + myOrder.getWhen());
sb.append(" Items: " + myOrder.getItems());
sb.append(" ");
try {
PrintWriter printWriter = httpServletResponse.getWriter();
printWriter.write(sb.toString());
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
\end{verbatim}
\end{enumerate}
For this example you rendered everything through a
\texttt{StringBundler}. In more complex cases, you would use JSPs or
another templating technology.
The renderer is ready for use! In 7.0, Info Item Renderers are not
widely used, but the usages and application will grow in future
releases.
\chapter{Using Providers with Custom
Applications}\label{using-providers-with-custom-applications}
Imagine a widget that can display lists of orders. You can use the Info
Framework so that it shows any list of orders provided through
\texttt{InfoListProvider}.
First you must obtain a list of all available providers for the desired
type, and then you would obtain a specific provider through that list.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The list of all available providers for \texttt{MyOrder}, can be
obtained done by using the \texttt{InfoListProviderTracker}:
\begin{verbatim}
@Reference
InfoListProviderTracker _infoListProviderTracker;
\end{verbatim}
Once a tracker is available, obtaining the list is as simple as
invoking \texttt{getInfoListProviders()}:
\begin{verbatim}
_infoListProviderTracker.getInfoListProviders(MyOrder.class);
\end{verbatim}
When the user selects an item from this list, you can store the
class's name.
\item
When a specific provider is desired it can be obtained through its
class name as follows:
\begin{verbatim}
_infoListProviderTracker.getInfoListProvider(infoListProviderClassName);
\end{verbatim}
\end{enumerate}
\section{Leveraging renderers from a custom
application}\label{leveraging-renderers-from-a-custom-application}
Using renderers from a custom application is almost identical to using
providers. Here is the equivalent code to what you've seen previously:
\begin{verbatim}
_infoItemRendererTracker.getInfoItemRenderers(MyOrder.class.getName());
String infoItemRendererClassName = MyOrderRenderer.class.getName();
_infoItemRendererTracker.getInfoItemRenderer(infoItemRendererClassName);
\end{verbatim}
\chapter{Liferay Forms}\label{liferay-forms}
The Liferay Forms application is a full-featured form building tool for
collecting data. There's lots of built-in functionality, and for the
pieces you're missing, there's lots of extensibility.
\chapter{Form Serialization with the DDM IO
API}\label{form-serialization-with-the-ddm-io-api}
When a form creator saves a form in the Liferay Forms application, the
Form object is \emph{serialized} (converted) into JSON for storage in
the Liferay DXP database. That's the default behavior; if you need the
form in a different format, you must write your own serialization and
deserialization code. Why would you want to do that? Maybe you think
JSON storage is not secure, or you have another tool that can consume
the form if it's in YAML. Whatever your reasons, the form can be stored
in any format as long as you write a \texttt{DDMFormSerializer} and its
corresponding \texttt{DDMFormDeserializer} with the proper logic.
First consider what form data looks like by default, in JSON. A simple
form, \emph{My Form}, with one text field, \emph{Full Name}, is first
created as a \texttt{DDMForm} Java object, then \emph{serialized} into
JSON for storage in the Liferay DXP database when saved.
\begin{verbatim}
{
"availableLanguageIds":["en_US"],
"successPage":{"body":{},
"title":{},
"enabled":false},
"defaultLanguageId":"en_US",
"fields":[{
"autocomplete":false,
"ddmDataProviderInstanceId":"[]",
"dataType":"string",
"predefinedValue":{"en_US":""},
"tooltip":{"en_US":""},
"readOnly":false,
"label":{"en_US":"Full Name"},
"type":"text",
"required":false,
"showLabel":true,
"displayStyle":"singleline",
"fieldNamespace":"",
"indexType":"keyword",
"visibilityExpression":"",
"ddmDataProviderInstanceOutput":"[]",
"repeatable":false,
"name":"FullName",
"options":[{"label":{"en_US":"Option"},"value":"Option"}],
"localizable":true,
"tip":{"en_US":""},
"placeholder":{"en_US":""},
"dataSourceType":"",
"validation":{"expression":"","errorMessage":""}
}]
}
\end{verbatim}
From its initial state as a \texttt{DDMForm} Java object, the form is
\emph{serialized} into JSON format, and upon retrieval from the
database, it's \emph{deserialized}: the JSON object representing the
form is translated back into a \texttt{DDMForm} Java object, with all
its requisite fields. For example, the JSON for the above example holds
each form field in the \texttt{fields} attribute. To translate this back
into the necessary \texttt{DDMForm} object, first parse the data
contained in the JSON object into an actual form field using your
deserialization logic. Here's the logic from
\texttt{DDMFormJsonDeserializer} that parses the JSON \texttt{"fields"}
element into a list of \texttt{DDMFormFields}:
\begin{verbatim}
protected List getDDMFormFields(JSONArray jsonArray)
throws PortalException {
List ddmFormFields = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
DDMFormField ddmFormField = getDDMFormField(
jsonArray.getJSONObject(i));
ddmFormFields.add(ddmFormField);
}
return ddmFormFields;
}
\end{verbatim}
Now calling \texttt{DDMForm.setDDMFormFields(ddmFormFields)} in the
deserializer completes the translation process from the JSON array back
to a \texttt{DDMFormField} object that the \texttt{DDMForm} needs.
If you'd like to store forms in a different format, provide custom
\emph{serialization} and \emph{deserialization} functionality.
\chapter{Serializing Forms}\label{serializing-forms}
The DDM IO API serializes and deserializes forms using a
request/response structure. The example here creates a serializer for
saving form data in \href{https://yaml.org}{YAML} format. The same
principles shown here apply to writing a deserializer.
To serialize form data into YAML:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a class that implements \texttt{DDMFormSerializer}:
\begin{verbatim}
@Component(immediate = true, property = "ddm.form.serializer.type=yaml") public
class DDMFormYamlSerializer implements DDMFormSerializer { ..... }
\end{verbatim}
The property \texttt{ddm.form.serializer.type=yaml} marks the
Component so that \texttt{DDMFormSerializerTracker} can find the YAML
serializer.
\item
Add the serializing logic to the overridden \texttt{serialize} method.
It takes a \texttt{DDMFormSerializerSerializeRequest} and returns a
\texttt{DDMFormSerializerSerializeResponse} with the serialized string
in it.
\begin{verbatim}
@Override public DDMFormSerializerSerializeResponse serialize(
DDMFormSerializerSerializeRequest ddmFormSerializerSerializeRequest) {
DDMForm ddmForm = ddmFormSerializerSerializeRequest.getDDMForm();
...YOUR CODE FOR BUILDING A YAML OBJECT GOES HERE ...
DDMFormSerializerSerializeResponse.Builder builder =
DDMFormSerializerSerializeResponse.Builder.newBuilder(yamlObject.toString());
return builder.build(); }
\end{verbatim}
\end{enumerate}
This is what you need to create your serializer. Of course,
\texttt{YOUR\ CODE\ FOR\ BUILDING\ A\ YAML\ OBJECT\ GOES\ HERE} requires
some explanation. While you can do whatever you want here, there are
several things you really ought to do:
\textbf{Add the available Language IDs:} Since you have the
\texttt{DDMForm} object from the request, call
\texttt{ddmForm.getAvailableLocales()}.
\textbf{Add the default Language ID:} Get this from the \texttt{DDMForm}
object by calling \texttt{ddmForm.getDefaultLocale()}.
\textbf{Add the Form Fields:} Get these from the \texttt{DDMForm} object
by calling \texttt{ddmForm.getDDMFormFields()}.
\textbf{Add any Form Rules:} Get them form the \texttt{DDMForm} object
with \texttt{ddmForm.getDDMFormRules()}.
\textbf{Add Success Page Settings:} Get these from the \texttt{DDMForm}
with \texttt{ddmForm.getDDMFormSuccessPageSettings()}.
All these are done in the default form serializer,
\texttt{DDMFormJSONSerializer}.
If you have the Liferay DXP source code, you can find the default
serializer in
\begin{verbatim}
modules/apps/dynamic-data-mapping/dynamic-data-mapping-io/src/main/java/com/liferay/dynamic/data/mapping/io/internal/DDMFormJSONSerializer.java
\end{verbatim}
You didn't create serialization code for no reason. You'll want to call
it from somewhere.
\section{Calling the Serializer}\label{calling-the-serializer}
To get properly serialized form content, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get the serializer from the \texttt{DDMFormSerializerTracker}, passing
in the value of the \texttt{ddm.form.serializer.type} property.
\item
Construct a \texttt{DDMFormSerializerSerializeRequest} object using
its nested static \texttt{Builder} class.
\item
Call the \texttt{serialize} method you wrote in the last section to
create the \texttt{DDMFormSerializerSerializeResponse}, passing the
\texttt{DDMFormSerializerSerializeRequest} object, via a call to the
\texttt{Builder}'s \texttt{build} method.
\item
Get the serialized form content from the
\texttt{DDMFormSerializerSerializeResponse} by calling its
\texttt{getContent} method.
\end{enumerate}
Here's a code example:
\begin{verbatim}
DDMFormSerializer ddmFormSerializer =
ddmFormSerializerTracker.getDDMFormSerializer("yaml");
DDMFormSerializerSerializeRequest.Builder builder =
DDMFormSerializerSerializeRequest.Builder.newBuilder(ddmForm);
DDMFormSerializerSerializeResponse ddmFormSerializerSerializeResponse =
ddmFormSerializer.serialize(builder.build());
ddmFormSerializerSerializeResponse.getContent();
\end{verbatim}
You can create a serializer for any format that can be saved in the
database as a String. Once you create the serializer, make it the
default by changing the storage format in the Form's Settings menu.
\chapter{Localization}\label{localization}
If you're writing a Liferay application, you're probably a genius who is
also really cool, which means your application will be used throughout
the entire world. At least, if its messages can be translated into their
language, it will. Thankfully, Liferay facilitates creating and using
message translations and adapting to cultural conventions for user names
and initials.
You can leverage Liferay's localization framework or use standard
resource bundles to localize your app. The localization framework uses
properties files (the same as any resource bundle) but leverages a
default properties file called \texttt{Language.properties} to propagate
messages (language keys) to properties files for all your locales. For
example, when you add a new message to the \texttt{Language.properties}
file and run Language Builder, it propagates the message to your locale
files. All you must do is translate the message in each locale file,
manually or automatically using Language Builder.
Language Builder integrates the Microsoft Text Translator API to
translate each locale file's messages from your default locale to the
respective locale. A machine's translation is no substitute for a
human's, of course, but the automatic translation gives you a base to
work from.
It's common to use the same messages in multiple apps. Liferay DXP
provides these message sharing features:
\begin{itemize}
\item
Liferay DXP's messages (and their translations) are available for all
your apps to use. JSP tags such as
\texttt{\textless{}liferay-ui:message\ ...\ /\textgreater{}} let you
use all Liferay DXP messages.
\item
Language modules are easy to use in all your apps. They're great for
centralizing messages in all your locales.
\end{itemize}
Lastly, Liferay DXP provides settings for adapting your app to cultures:
\begin{itemize}
\item
Naming conventions for users
\item
Initial conventions for user avatars
\item
Text direction settings (left-to-right or right-to-left)
\end{itemize}
Localization is important to all site users. Liferay helps you get it
right! Start with localizing your application using Liferay's
localization framework.
\chapter{Localizing Your Application}\label{localizing-your-application}
Liferay's localization framework helps you create and use localized
messages in minutes. You create your messages in a default properties
file called \texttt{Language.properties} and localize them in properties
files that use the convention \texttt{Language\_xx.properties}, where
\texttt{xx} is the locale code. After deploying your app, the messages
are available to your templates. Liferay's JSP tags, such as
\texttt{\textless{}liferay-ui:message\ .../\textgreater{}} display them
in the user's current locale automatically, without requiring you to
access \texttt{ResourceBundle} or \texttt{Locale} objects explicitly.
Here are the steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Create a default language properties file called
\texttt{Language.properties} in your project's resource bundle folder.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
Project Type | Resource Bundle Folder |
------------------------- | ---------------------- |
Bean Portlet | `src/main/resources/content/` |
JSF Portlet | `src/main/resources/` |
Liferay MVC Portlet | `src/main/resources/content/` |
PortletMVC4Spring Portlet | `src/main/java/resources/content/` |
Angular Widget | `features/localization/` |
React Widget | `features/localization/` |
Vue.js Widget | `features/localization/` |
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Specify your language properties (language keys) using key/value
pairs. For example, create a friendly greeting.
\begin{verbatim}
howdy-partner=Howdy, Partner!
\end{verbatim}
\item
Configure your app's resource bundle and the locales you're
supporting. The locales your Liferay DXP instance supports are
specified in its
\href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal
properties} file (here are Liferay DXP's
\href{(https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Languages\%20and\%20Time\%20Zones)}{default
locales}. For example, these configurations support translations for
English and Spanish locales:
\textbf{\texttt{@PortletConfiguration} class annotation:} Can be used
in Portlet 3.0 portlets such as Bean Portlets.
\begin{verbatim}
@PortletConfiguration (
...
resourceBundle="content.Language",
supportedLocales = {"en", "es"}
)
\end{verbatim}
\textbf{Portlet descriptor \texttt{portlet.xml}:} Can be used in any
portlet WAR.
\begin{verbatim}
...
en
es
content.Language
...
\end{verbatim}
\textbf{\texttt{@Component} class annotation:} Can be used in a
portlet module such as a Liferay MVC Portlet.
\begin{verbatim}
@Component (
...
property = {
...
"javax.portlet.supported-locale=en",
"javax.portlet.supported-locale=es",
"javax.portlet.resource-bundle=content.Language"
}
)
\end{verbatim}
\item
Create language properties for a locale. For demonstration purposes,
create one manually.
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-generating-translations}{Automatically
generating translations} is discussed later.
For example, create a Spanish translation by copying
\texttt{Language.properties} to \texttt{Language\_es.properties} and
translating the property values to Spanish.
\begin{verbatim}
howdy-partner=Hola, Compañero!
\end{verbatim}
\item
In your front-end template code, use the language property. For
example, a JSP could use the \texttt{howdy-partner} property via the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/message.html}{\texttt{\textless{}liferay-ui:message\ /\textgreater{}}}
tag.
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
...
...
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Tip:** The
[`liferay-ui`](https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/tld-summary.html)
tag library has several tags (e.g., `alert`, `error`, and `message`) that
accept language keys.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
Deploy your application and view it in different locales. For example,
you could view the app locally in Spanish by specifying the
\texttt{es} locale code in the URL (e.g.,
\texttt{http://localhost:8080/es/...}).
\end{enumerate}
Congratulations on a great start to localizing your application!
Next, you can explore
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-generating-translations}{generating
translations automatically} or
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-language-module}{create
a language module} for using language keys across applications.
\section{Related Topics}\label{related-topics-91}
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-generating-translations}{Automatically
Generating Translations}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module}{Using
Language Modules}
\href{/docs/7-2/frameworks/-/knowledge_base/f//docs/7-1/tutorials/-/knowledge_base/t/using-liferays-language-settings}{Using
Liferay DXP's Language Settings}
\chapter{Using Liferay's Localization
Settings}\label{using-liferays-localization-settings}
You can customize a given locale's default language settings by
overriding the properties that control those settings. For instructions
on this, see
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{Overriding
Language Keys}. Here, you'll learn which properties correspond to common
language settings.
So what all can be customized? This is an excellent question! Consider
these examples:
\begin{itemize}
\item
In the add and edit user forms, you can configure the name fields that
are displayed and the field values available in select fields. For
example, you can leave out the middle name field or alter the prefix
selections.
\item
You can also control the directionality of content and messages (left
to right or right to left).
\end{itemize}
Language properties exist in \texttt{Language\_xx.properties} files,
where \texttt{xx} represents the locale. For example,
\texttt{Language\_es.properties} contains the properties for Spanish,
\texttt{Language\_en.properties} contains the properties for English,
and so on.
The default (core) language properties are in
\texttt{Language.properties}. \textbf{Do not edit this file.} You can,
however, open it to view the default language settings. There are two
ways to do so:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
From Liferay Portal's source code. Navigate to
\begin{verbatim}
liferay-portal/portal-impl/src/content/Language.properties
\end{verbatim}
\item
From a bundle's \texttt{portal-impl.jar}.
\begin{verbatim}
[Liferay Home]/tomcat-[version]/webapps/ROOT/WEB-INF/lib/portal-impl.jar
\end{verbatim}
Open the \texttt{content} folder in the JAR to find the language
files.
\end{enumerate}
The first section in the core \texttt{Language.properties} file is
labeled \emph{Language Settings} and contains language properties that
begin with \texttt{lang}:
\begin{verbatim}
##
## Language Settings
##
lang.dir=ltr
lang.line.begin=left
lang.line.end=right
lang.user.default.portrait=initials
lang.user.initials.field.names=first-name,last-name
lang.user.name.field.names=prefix,first-name,middle-name,last-name,suffix
lang.user.name.prefix.values=Dr,Mr,Ms,Mrs
lang.user.name.required.field.names=last-name
lang.user.name.suffix.values=II,III,IV,Jr,Phd,Sr
\end{verbatim}
To use the language settings mentioned here, you must have a module. See
the articles on
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{overriding
language keys} to set up a module with the following characteristics:
\begin{itemize}
\item
Contains an implementation of \texttt{ResourceBundle} that is
registered in the OSGi runtime.
\item
Contains a \texttt{Language\_xx.properties} file for the locale whose
properties you want to override.
\end{itemize}
\section{Localizing User Names}\label{localizing-user-names}
Naming conventions can differ between locales. For example, users in
some locales have more than one last name. You can therefore change the
user name properties to fit the given locale. The properties for
changing user name settings begin with \texttt{lang.user.name}.
User name fields are configurable in the following ways:
\begin{itemize}
\item
Remove certain name fields and make others appear more than once. Some
locales need more than one last name, for example.
\begin{verbatim}
lang.user.name.field.names=prefix,first-name,middle-name,last-name,suffix
\end{verbatim}
\item
Change the prefix and suffix values for a locale.
\begin{verbatim}
lang.user.name.prefix.values=Dr,Mr,Ms,Mrs
lang.user.name.suffix.values=II,III,IV,Jr,Phd,Sr
\end{verbatim}
\item
Specify which fields are required.
\begin{verbatim}
lang.user.name.required.field.names=last-name
\end{verbatim}
\end{itemize}
A user's first name is mandatory. Because of this, take these two points
into consideration when configuring a locale's user name settings:
\begin{itemize}
\item
The \texttt{first-name} field can't be removed from the field names
list.
\begin{verbatim}
lang.user.name.field.names=prefix,first-name,middle-name,last-name,suffix
\end{verbatim}
\item
Because a first name is required, it's always implicitly included in
the \texttt{required\ field\ names} property:
\begin{verbatim}
lang.user.name.required.field.names=last-name
\end{verbatim}
Therefore, any fields you enter here are \emph{in addition to} the
first name field. Last name is required by default, but you can
disable it by deleting its value from the property:
\begin{verbatim}
lang.user.name.required.field.names=
\end{verbatim}
In that case, only a first name would be required.
\end{itemize}
For most of the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Languages\%20and\%20Time\%20Zones}{locales
enabled by default}, the user name properties are tailored to each
locale. The default locales are specified by the
\texttt{locales.enabled} property:
\begin{verbatim}
locales.enabled=ar_SA,ca_ES,zh_CN,nl_NL,en_US,fi_FI,fr_FR,de_DE,hu_HU,ja_JP,pt_BR,es_ES,sv_SE
\end{verbatim}
For example, here are the English (\texttt{Language\_en.properties})
properties for setting user name fields:
\begin{verbatim}
lang.user.name.field.names=prefix,first-name,middle-name,last-name,suffix
lang.user.name.prefix.values=Dr,Mr,Ms,Mrs
lang.user.name.required.field.names=last-name
lang.user.name.suffix.values=II,III,IV,Jr,Phd,Sr
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/english-user-name-fields.png}
\caption{The user name settings impact the appearance of user
information and forms.}
\end{figure}
Compare those to the Spanish (\texttt{Language\_es.properties})
settings:
\begin{verbatim}
lang.user.name.field.names=prefix,first-name,last-name
lang.user.name.prefix.values=Sr,Sra,Sta,Dr,Dra
lang.user.name.required.field.names=last-name
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/spanish-user-name-fields.png}
\caption{The Spanish user name settings omit the suffix and middle name
fields.}
\end{figure}
The biggest difference between the English and Spanish form fields is
that the middle name and suffix fields are omitted in the Spanish
configuration. Other differences include the specific prefix values.
¡Muy excelente!
\section{Identifying User Initials}\label{identifying-user-initials}
The default avatar displays a user's initials. Some cultures use
initials differently, so there's a way to configure them in the
appropriate \texttt{Language\_xx.properties} file.
\begin{verbatim}
lang.user.default.portrait=initials
lang.user.initials.field.names=first-name,last-name
\end{verbatim}
The \texttt{lang.user.default.portrait} property sets the type of
portrait to use for users. This can be set to \texttt{initials} or
\texttt{image}. If set to \texttt{image}, the default images defined by
the \texttt{image.default.user.female.portrait} or
\texttt{image.default.user.male.portrait} properties residing in the
\texttt{portal.properties} file are used. Therefore, the
\texttt{lang.user.initials.field.names} property is ignored.
\begin{figure}
\centering
\includegraphics{./images/initials-avatar.png}
\caption{The user's initials are displayed for their avatar by default.}
\end{figure}
If you're leveraging the user's initials for the default avatar, you can
use the \texttt{lang.user.initials.field.names} property to organize how
the initials are displayed. Valid values for this property include
\texttt{first-name}, \texttt{middle-name}, and \texttt{last-name}, in
any order.
\section{Right to Left or Left to
Right?}\label{right-to-left-or-left-to-right}
The first three properties in the core \texttt{Language.properties}
file's Language Settings section change the display direction of a
language's characters. Most languages read from left to right, but some
languages read from right to left (e.g., Arabic, Hebrew, and Persian).
Here are the relevant language properties for a right-to-left language:
\begin{verbatim}
lang.dir=rtl
lang.line.begin=right
lang.line.end=left
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} You can prevent specific CSS rules from transforming
(flipping) with the \texttt{/*\ @noflip\ */} decoration. Place the
decoration to the left of the CSS rule to apply it. For example, this
rule gives a left margin of \texttt{20em} to the \texttt{body} no matter
if the selected language is LTR or RTL:
\begin{verbatim}
/* @noflip */ body {
margin-left: 20em;
}
\end{verbatim}
You can also use the \texttt{.rtl} CSS selector for rules that
exclusively apply to RTL languages.
\noindent\hrulefill
\section{Related Topics}\label{related-topics-92}
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{Overriding
Language Keys}
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{Localizing
Your Application}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-language-module}{Creating
a Language Module}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module}{Using
a Language Module}
\chapter{Creating a Language Module}\label{creating-a-language-module}
You might have an application with multiple modules that provide the
view layer. These modules are often called web modules. For example,
this application contains three such modules:
\begin{verbatim}
my-application/
my-application-web/
my-admin-application-web/
my-application-content-web/
my-application-api/
my-application-service/
\end{verbatim}
Each of these modules can have language keys and translations to
maintain, likely resulting in duplicate keys. You don't want to end up
with different values for the same key, and you don't want to maintain
language keys in multiple places. In this case, you should create a
single module---a language module---for housing all your app's language
keys.
Follow these steps to create a language module:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the root project folder (the one that holds your service, API, and
web modules),
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{create
a new module} to hold your app's language keys.
\item
In the language module, create a \texttt{src/main/resources/content}
folder. In this folder, create a \texttt{Language.properties} file for
your app's default language keys. For example, such a file might look
like this:
\begin{verbatim}
my-app-title=My Application
add-entity=Add Entity
\end{verbatim}
\item
Create any translations you want in additional language properties
files, appending the locale's ID to the file name. For example, a file
\texttt{Language\_es.properties} holds Spanish (\texttt{es})
translations and could contain something like this:
\begin{verbatim}
my-app-title=Mi Aplicación
add-entity=Añadir Entity
\end{verbatim}
Here's the folder structure of an example language module called
\texttt{my-application-lang}. This module contains the app's default
language keys (\texttt{Language.properties}) and a Spanish translation
(\texttt{Language\_es.properties}):
\begin{verbatim}
my-application-lang/
bnd.bnd
src/
main/
resources/
content/
Language.properties
Language_es.properties
...
\end{verbatim}
\end{enumerate}
On building the language module, Liferay DXP's
\texttt{ResourceBundleLoaderAnalyzerPlugin} detects the module's
\texttt{Language.properties} file and adds a resource bundle
\href{http://blog.osgi.org/2015/12/using-requirements-and-capabilities.html}{capability}
to the module. A capability is a contract a module declares to the OSGi
framework. Capabilities let you associate services with modules that
provide them. In this case, Liferay DXP registers a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ResourceBundleLoader.html}{ResourceBundleLoader}
service for the resource bundle capability.
\section{Related Topics}\label{related-topics-93}
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{Overriding
Language Keys}
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{Localizing
Your Application}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-liferays-localization-settings}{Using
Liferay's Localization Settings}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module}{Using
a Language Module}
\chapter{Using a Language Module}\label{using-a-language-module}
A module or traditional Liferay plugin can use a resource bundle from
another module and optionally include its own resource bundle. OSGi
manifest headers \texttt{Require-Capability} and
\texttt{Provide-Capability} make this possible, and it's especially easy
in modules generated from Liferay project templates. Instructions for
using a language module are divided into these environments:
\begin{itemize}
\tightlist
\item
\hyperref[using-a-language-module-from-a-module]{Using a Language
Module from a Module}
\item
\hyperref[using-a-language-module-from-a-traditional-plugin]{Using a
Language Module from a Traditional Plugin}
\end{itemize}
If you're using bnd with Maven or Gradle, you need only specify
Liferay's \texttt{-liferay-aggregate-resource-bundle:} bnd
instruction---at build time, Liferay's bnd plugin converts the
instruction to \texttt{Require-Capability} and
\texttt{Provide-Capability} parameters automatically. Both approaches
are demonstrated here.
\section{Using a Language Module from a
Module}\label{using-a-language-module-from-a-module}
Modules generated from Liferay project templates have a Liferay bnd
build time instruction called
\texttt{-liferay-aggregate-resource-bundles}. It lets you use other
resource bundles (including their language keys) along with your own.
Here's how to use this bnd instruction:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your module's \texttt{bnd.bnd} file.
\item
Add the \texttt{-liferay-aggregate-resource-bundles:} bnd instruction
and assign it the bundle symbolic names of modules whose resource
bundles you wish to aggregate with the current module's resource
bundle:
\begin{verbatim}
-liferay-aggregate-resource-bundles: \
[bundle.symbolic.name1],\
[bundle.symbolic.name2]
\end{verbatim}
\end{enumerate}
For example, a module that uses resource bundles from modules
\texttt{com.liferay.docs.l10n.myapp1.lang} and
\texttt{com.liferay.docs.l10n.myapp2.lang} would set this in its
\texttt{bnd.bnd} file:
\begin{verbatim}
-liferay-aggregate-resource-bundles: \
com.liferay.docs.l10n.myapp1.lang,\
com.liferay.docs.l10n.myapp2.lang
\end{verbatim}
The current module's resource bundle is prioritized over those of the
listed modules.
\noindent\hrulefill
\textbf{Note:} The Shared Language Key
\href{/docs/7-2/reference/-/knowledge_base/r/shared-language-keys}{sample
project} is a working example that demonstrates aggregating resource
bundles. You can deploy it in Gradle, Maven, and Liferay Workspace build
environments.
\noindent\hrulefill
At build time, Liferay's bnd plugin converts the bnd instruction to
\texttt{Require-Capability} and \texttt{Provide-Capability} parameters
automatically. In traditional Liferay plugins, you must specify the
parameters manually.
\noindent\hrulefill
\textbf{Note:} You can always specify the \texttt{Require-Capability}
and \texttt{Provide-\ \ Capability} OSGi manifest headers manually, as
the next section demonstrates.
\noindent\hrulefill
\section{Using a Language Module from a Traditional
Plugin}\label{using-a-language-module-from-a-traditional-plugin}
To use a language module from a traditional Liferay plugin you must
specify the language module via the \texttt{Require-Capability} and
\texttt{Provide-Capability} OSGi manifest headers in the plugin's
\texttt{liferay-plugin-package.properties} file.
Follow these steps to configure your traditional plugin to use a
language module:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the plugin's \texttt{liferay-plugin-package.properties} file and
add a \texttt{Require-Capability} header that filters on the language
module's resource bundle capability. For example, if the language
module's symbolic name is \texttt{myapp.lang}, specify the requirement
like this:
\begin{verbatim}
Require-Capability: liferay.resource.bundle;filter:="(bundle.symbolic.name=myapp.lang)"
\end{verbatim}
\item
In the same \texttt{liferay-plugin-package.properties} file, add a
\texttt{Provide-Capability} header that adds the language module's
resource bundle as this plugin's (the \texttt{myapp.web} plugin) own
resource bundle:
\begin{verbatim}
Provide-Capability:\
liferay.resource.bundle;resource.bundle.base.name="content.Language",\
liferay.resource.bundle;resource.bundle.aggregate:String="(bundle.symbolic.name=myapp.lang)";bundle.symbolic.name=myapp.web;resource.bundle.base.name="content.Language";service.ranking:Long="4";\
servlet.context.name=myapp-web
\end{verbatim}
\end{enumerate}
In this case, the \texttt{myapp.web} plugin solely uses the language
module's resource bundle---the resource bundle aggregate only includes
language module \texttt{myapp.lang}.
Aggregating resource bundles comes into play when you want to use a
language module's resource bundle in addition to your plugin's resource
bundle. These instructions show you how to do this, while prioritizing
your current plugin's resource bundle over the language module resource
bundle. In this way, the language module's language keys compliment your
plugin's language keys.
For example, a portlet whose bundle symbolic name is \texttt{myapp.web}
uses keys from the language module \texttt{myapp.lang} in addition to
its own. The portlet's \texttt{Provide-Capability} and
\texttt{Web-ContextPath} OSGi headers accomplish this.
\begin{verbatim}
Provide-Capability:\
liferay.resource.bundle;resource.bundle.base.name="content.Language",\
liferay.resource.bundle;resource.bundle.aggregate:String="(bundle.symbolic.name=myapp.web),(bundle.symbolic.name=myapp.lang)";bundle.symbolic.name=myapp.web;resource.bundle.base.name="content.Language";service.ranking:Long="4";\
servlet.context.name=myapp-web
\end{verbatim}
The example \texttt{Provide-Capability} header has two parts:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\texttt{liferay.resource.bundle;resource.bundle.base.name="content.Language"}
declares that the module provides a resource bundle whose base name is
\texttt{content.language}.
\item
The
\texttt{liferay.resource.bundle;resource.bundle.aggregate:String=...}
directive specifies the list of bundles whose resource bundles are
aggregated, the target bundle, the target bundle's resource bundle
name, and this service's ranking:
\begin{itemize}
\tightlist
\item
\texttt{"(bundle.symbolic.name=myapp.web),(bundle.symbolic.name=myapp.lang)"}:
The service aggregates resource bundles from bundles
\texttt{bundle.symbolic.name=myapp.web} (the current module) and
\texttt{bundle.symbolic.name=myapp.lang}. Aggregate as many bundles
as desired. Listed bundles are prioritized in descending order.
\item
\texttt{bundle.symbolic.name=myapp.web;resource.bundle.base.name="content.Language"}:
Override the \texttt{myapp.web} bundle's resource bundle named
\texttt{content.Language}.
\item
\texttt{service.ranking:Long="4"}: The resource bundle's service
ranking is \texttt{4}. The OSGi framework applies this service if it
outranks all other resource bundle services that target
\texttt{myapp.web}'s \texttt{content.Language} resource bundle.
\item
\texttt{servlet.context.name=myapp-web}: The target resource bundle
is in servlet context \texttt{myapp-web}.
\end{itemize}
\end{enumerate}
Now the language keys from the aggregated resource bundles compliment
your plugin's language keys.
\section{Related Topics}\label{related-topics-94}
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{Localizing
Your Application}
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{Overriding
Language Keys}
\chapter{Automatically Generating
Translations}\label{automatically-generating-translations}
If your app uses a
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module}{language
module} or
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{\texttt{Language.properties}
file} for its user interface messages, you're in the right place.
Language Builder provides these localization capabilities:
\begin{itemize}
\item
Generating language files for each supported locale with a single
command. It also propagates new default language file keys to all
language files, while keeping their translated values intact.
\item
Generating translations automatically using Microsoft's Translator
Text API. This gives you a jump start on creating translations.
\end{itemize}
\noindent\hrulefill
\textbf{Note:} Language Builder is available as a plugin for projects
that use Gradle or Maven.
\noindent\hrulefill
Start with Configuring the Language Builder plugin.
\section{Configuring the Language Builder
Plugin}\label{configuring-the-language-builder-plugin}
Configure the Language Builder plugin for
\href{/docs/7-2/reference/-/knowledge_base/r/lang-builder-gradle-plugin}{Gradle}
or
\href{/docs/7-2/reference/-/knowledge_base/r/lang-builder-plugin}{Maven}.
\textbf{Gradle:}
\begin{verbatim}
buildscript {
dependencies {
classpath 'com.liferay:com.liferay.gradle.plugins.lang.builder:latest.release'
}
repositories {
maven {
url "https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/"
}
}
}
apply plugin: "com.liferay.lang.builder"
repositories {
maven {
url "https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/"
}
}
\end{verbatim}
\textbf{Maven:}
\begin{verbatim}
...
com.liferay
com.liferay.lang.builder
1.0.30
.
${microsoft.translator.client.id}
${microsoft.translator.client.secret}
...
\end{verbatim}
Now you can invoke Language Builder in your project.
\section{Running Language Builder}\label{running-language-builder}
When you run Language Builder, it generates properties files for all
your locales and propagates all new language properties from
\texttt{Language.properties} to your locale language files (newly
generated and existing). Additionally if you configured the Microsoft
Translator Text API (discussed next), Language Builder translates your
locale language properties.
Here's the command:
\textbf{Gradle:}
\begin{verbatim}
gradlew buildLang
\end{verbatim}
\textbf{Maven:}
\begin{verbatim}
mvn lang-builder:build
\end{verbatim}
\noindent\hrulefill
\textbf{Tip:} Run Language Builder to update your locale files each time
you change your \texttt{Language.properties} file.
\noindent\hrulefill
Note, until you configure translation credentials (discussed next),
Language Builder prints this message:
\begin{verbatim}
Translation is disabled because credentials are not specified
\end{verbatim}
If you want to configure your app to generate automatic translations
using the Microsoft Translator Text API, keep reading.
\section{Translating Language Keys
Automatically}\label{translating-language-keys-automatically}
If you've configured the Language Builder plugin (above) in your
project, you're well on your way to translating language keys
automatically. Now you have to configure
\href{https://azure.microsoft.com/en-us/services/cognitive-services/translator-text-api/}{Microsoft's
Translator Text API} so you can translate language keys automatically.
\noindent\hrulefill
\textbf{Important:} Lang Builder does not translate language keys
containing HTML (e.g., \texttt{\textless{}em\textgreater{}},
\texttt{\textless{}b\textgreater{}},
\texttt{\textless{}code\textgreater{}}, etc.). Default language keys
that contain HTML are only \emph{copied} to your locale language files.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Note:} These translations are best used as a starting point. A
machine translation can't match the accuracy of a real person who is
fluent in the language. Then again, if you only speak English and you
need a Hungarian translation, this is better and faster than your
attempts at a manual translation.
\noindent\hrulefill
Here's how to set up the translator and generate translations.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Generate a translation subscription key for the Microsoft Translator
Text API. Follow the instructions
\href{https://www.microsoft.com/en-us/translator/business/}{here}.
\item
Add your client credentials to the Language Builder plugin
configuration. For security reasons, pass the credentials to a
property that's stored in your local build environment (e.g., see the
\href{https://docs.gradle.org/current/userguide/build_environment.html}{Gradle
environment guide}).
\textbf{Gradle:}
Make sure the \texttt{buildLang} task knows to use your subscription
key for translation by setting the \texttt{translateSubscriptionKey}
property:
\begin{verbatim}
buildLang {
translateSubscriptionKey = langTranslateSubscriptionKey
}
\end{verbatim}
Here's the entire \texttt{build.gradle} example code,
\begin{verbatim}
buildscript {
dependencies {
classpath 'com.liferay:com.liferay.gradle.plugins.lang.builder:latest.release'
}
repositories {
maven {
url "https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/"
}
}
}
apply plugin: "com.liferay.lang.builder"
buildLang {
translateSubscriptionKey = langTranslateSubscriptionKey
}
repositories {
maven {
url "https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/"
}
}
\end{verbatim}
\textbf{Maven:}
Set the following Language Builder plugin
\texttt{\textless{}translateClientId\ /\textgreater{}} and
\texttt{\textless{}translateClientSecret\ /\textgreater{}}
configuration elements using Maven build environment properties:
\begin{verbatim}
.
${microsoft.translator.client.id}
${microsoft.translator.client.secret}
...
\end{verbatim}
Here's the entire \texttt{pom.xml} example code,
\begin{verbatim}
...
com.liferay
com.liferay.lang.builder
1.0.30
.
${microsoft.translator.client.id}
${microsoft.translator.client.secret}
...
\end{verbatim}
\item
Run Language Builder.
\textbf{Gradle:}
\begin{verbatim}
gradlew buildLang
\end{verbatim}
\textbf{Maven:}
\begin{verbatim}
mvn lang-builder:build
\end{verbatim}
\end{enumerate}
Great! You can now generate language files and provide automatic
translations of your language keys.
\section{Related Topics}\label{related-topics-95}
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{Localizing
Your Application}
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module}{Using
Language Modules}
\href{/docs/7-2/reference/-/knowledge_base/r/lang-builder-gradle-plugin}{Gradle
Language Builder Plugin}
\href{/docs/7-2/reference/-/knowledge_base/r/lang-builder-plugin}{Maven
Language Builder Plugin}
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}
\href{/docs/7-2/reference/-/knowledge_base/r/tooling}{Tooling}
\chapter{Portlets}\label{portlets}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay DXP started off as a portal server, designed to serve Java-based
web applications called \emph{portlets} (see
\href{https://jcp.org/en/jsr/detail?id=168}{JSR 168},
\href{https://jcp.org/en/jsr/detail?id=286}{JSR-286}, and
\href{https://jcp.org/en/jsr/detail?id=362}{JSR-362}). Portlets process
requests and generate responses like any other web application. One key
difference, however, between portlets and other web apps is that
portlets run in a portion of the web page. When you're writing a portlet
application, you need only worry about that application: the rest of the
page---the navigation, the top banner, and any other global components
of the interface---is handled by other components. Portlets run only in
a portal server. They use the portal's existing support for user
management, authentication, permissions, page management, and more. This
frees you to focus on developing the portlet's core functionality. In
many ways, writing your application as a portlet is easier than writing
a standalone application.
Many portlets can be placed on a single page by users (if they have
permission) or portal administrators. For example, a page in a community
site could have a calendar portlet for community events, an
announcements portlet for important announcements, and a bookmarks
portlet for links of interest to the community. You can drag and drop to
reposition and resize portlets on a page without altering any portlet
code. Alternatively, a single portlet can take up an entire page if it's
the only app you need on that page. For example, message boards or Wikis
with complex user interfaces are best suited on their own pages. In
short, portlets alleviate many of the traditional pain points associated
with developing Java-based web apps.
\begin{figure}
\centering
\includegraphics{./images/portlet-applications.png}
\caption{You can place multiple portlets on a single page.}
\end{figure}
Portlets handle requests in multiple phases. This makes portlets much
more flexible than servlets. Each portlet phase executes different
operations:
\begin{itemize}
\tightlist
\item
\textbf{Render:} Generates the portlet's content based on its current
state. When this phase runs on one portlet, it also runs on all other
portlets on the page. The Render phase runs when any portlets on the
page complete the Action or Event phases.
\item
\textbf{Action:} In response to a user action, the Action phase
performs operations that change the portlet's state. The Action phase
can also trigger events that are processed by the Event phase.
Following the Action phase and optional Event phase, the Render phase
then regenerates the portlet's contents.
\item
\textbf{Event:} Processes events triggered in the Action phase. Events
are used for inter-portlet communication (IPC). Once the portlet
processes all events, the portal calls the Render phase on all
portlets on the page.
\item
\textbf{Resource-serving:} Serves a resource independently from the
rest of the lifecycle. This lets a portlet serve dynamic content
without running the Render phase on all portlets on a page. The
Resource-serving phase handles AJAX requests.
\item
\textbf{Header:} Lets you specify resource dependencies, such as CSS,
prior to the Render phase.
\end{itemize}
Compared to servlets, portlets also have some other key differences.
Since portlets only render a portion of a page, tags like
\texttt{\textless{}html\textgreater{}},
\texttt{\textless{}head\textgreater{}}, and
\texttt{\textless{}body\textgreater{}} aren't allowed. And because you
don't know the portlet's page ahead of time, you can't create portlet
URLs directly. Instead, the portlet API gives you methods to create
portlet URLs programmatically. Also, because portlets don't have direct
access to the \texttt{javax.servlet.ServletRequest}, they can't read
query parameters directly from a URL. Portlets instead access a
\texttt{javax.portlet.PortletRequest} object. The portlet specification
provides a mechanism for a portlet to read only its own URL parameters
or those declared as public render parameters. Liferay DXP does,
however, provide utility methods that can access the
\texttt{ServletRequest} and query parameters. Portlets also have a
\emph{portlet filter} available for each phase in the portlet lifecycle.
Portlet filters are similar to servlet filters in that they allow
request and response modification on the fly.
Portlets also differ from servlets by having distinct modes and window
states. Modes distinguish the portlet's current function:
\begin{itemize}
\tightlist
\item
\textbf{View mode:} The portlet's standard mode. Use this mode to
access the portlet's main functionality.
\item
\textbf{Edit mode:} The portlet's configuration mode. Use this mode to
configure a custom view or behavior. For example, the Edit mode of a
weather portlet might let you choose a location to retrieve weather
data from.
\item
\textbf{Help mode:} A mode that displays the portlet's help
information.
\end{itemize}
Most modern applications use View Mode only.
Portlet window states control the amount of space a portlet takes on a
page. Window states mimic window behavior in a traditional desktop
environment:
\begin{itemize}
\tightlist
\item
\textbf{Normal:} The portlet can be on a page that contains other
portlets. This is the default window state.
\item
\textbf{Maximized:} The portlet takes up an entire page.
\item
\textbf{Minimized:} Only the portlet's title bar shows.
\end{itemize}
All of the
\href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{ways to
develop web front-ends} on Liferay DXP involve portlets. The
JavaScript-based widgets use Liferay's JS Portlet Extender behind the
scenes and the Java-based web front-ends are explicitly portlets. All of
the web front-end types vary in their support of Portlet 3.0,
\href{/docs/7-2/frameworks/-/knowledge_base/f/dependency-injection}{dependency
injection (DI)}, Model View Controller (MVC), and modularity, giving you
plenty of good options for developing portlets.
\section{Related Topics}\label{related-topics-96}
\href{/docs/7-2/appdev/-/knowledge_base/a/portletmvc4spring}{Spring
Portlet MVC: PortletMVC4Spring}
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC Portlet}
\href{/docs/7-2/appdev/-/knowledge_base/a/jsf-portlet}{JSF Portlet}
\href{/docs/7-2/reference/-/knowledge_base/r/portlet-3-0-api-opt-in}{Portlet
3.0 API Opt In}
\chapter{Using JavaScript in Your
Portlets}\label{using-javascript-in-your-portlets}
Would you like to use the latest ECMAScript features in your JavaScript
files and portlets? Do you wish you could use npm and npm packages in
your portlets?
To use the ES2015+ syntax in a JavaScript file, add the extension
\texttt{.es} to its name. For example, you rename file
\texttt{filename.js} to \texttt{filename.es.js}. The extension indicates
it uses ES2015+ syntax and must therefore be transpiled by
\href{https://babeljs.io/}{Babel} before deployment.
ES2015+ advanced features, such as
\href{https://babeljs.io/docs/learn-es2015/\#generators}{generators},
are available to you if you import the \texttt{polyfillBabel} class from
the \texttt{polyfill-babel} module:
\begin{verbatim}
import polyfillBabel from 'polyfill-babel'
\end{verbatim}
The \href{http://babeljs.io/docs/usage/polyfill/}{Babel Polyfill}
emulates a complete ES6 environment. Use it at your own discretion, as
it loads a large amount of code. You can inspect
\url{https://github.com/zloirock/core-js\#core-js} to see what's
polyfilled.
Once you've completed writing your module, you can expose it by creating
a \texttt{package.json} file that specifies your bundle's name and
version. Make sure to create this in your module's root folder. Below is
an example \texttt{package.json} file for a \texttt{js-logger} module:
\begin{verbatim}
{
"name": "js-logger",
"version": "1.0.0"
}
\end{verbatim}
The Module Config Generator creates the module based on this
information. In this section, you'll learn how to prepare your
JavaScript files to leverage ECMAScript and npm features in your
portlets.
\chapter{Using ES2015 Modules in your
Portlet}\label{using-es2015-modules-in-your-portlet}
Once you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-javascript-in-your-portlets}{exposed
your modules} via your \texttt{package.json} file, you can use them in
your portlets. The \texttt{aui:script} tag's \texttt{require} attribute
makes it easy.
Follow the steps below to use your exposed modules in your portlets:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Declare the \texttt{aui} taglib in your view JSP:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** if you created the portlet using Blade, the `aui` taglib is
already provided for you in the `init.jsp`.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Add an \texttt{aui:script} tag to the JSP and set the \texttt{require}
attribute to the relative path for your module.
The \texttt{require} attribute lets you include your exposed modules
in your JSP. The AMD Loader fetches the specified module and its
dependencies. An example faux Console Logger Portlet's
\texttt{view.jsp} shown below includes the module \texttt{logger.es}:
\begin{verbatim}
var Logger = jsLoggerLoggerEs.default;
var loggerOne = new Logger('*** -> ');
loggerOne.log('Hello');
var loggerDefault = new Logger();
loggerDefault.log('World');
\end{verbatim}
References to the module within the script tag are named after the
\texttt{require} value, in camel-case and with all invalid characters
removed. The \texttt{logger.es} module's reference
\texttt{jsLoggerLoggerEs} is derived from the module's relative path
value \texttt{js-logger/logger.es}. The value is stripped of its dash
and slash characters and converted to camel case.
\end{enumerate}
Thanks to the \texttt{aui:script} tag and its \texttt{require}
attribute, using your modules in your portlet is a piece of cake!
\section{Related Topics}\label{related-topics-97}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps}{Customizing
JSPs}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/web-services}{Web
Services}
\end{itemize}
\chapter{Using npm in Your Portlets}\label{using-npm-in-your-portlets}
npm is a powerful tool, and almost a necessity for Front-End
development. You can use npm as your JavaScript package manager
tool---including npm and npm packages---while developing portlets in
your normal, everyday workflow.
Deployed portlets leverage
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-amd-modules-in-liferay}{Liferay
AMD Loader} to share JavaScript modules and take advantage of semantic
versioning when resolving modules among portlets on the same page. The
liferay-npm-bundler helps prepare your npm modules for the Liferay AMD
Loader.
The bundler copies the project and \texttt{node\_modules}' JS files to
the output and wraps them inside a \texttt{Liferay.Loader.define()} call
so that the Liferay AMD Loader knows how to handle them. It also
namespaces the module names in \texttt{require()} calls and inside the
\texttt{Liferay.Loader.define()} call with the project's name prefix to
achieve
\href{/docs/7-2/reference/-/knowledge_base/r/how-the-liferay-npm-bundler-publishes-npm-packages\#isolated-package-dependencies}{dependency
isolation}. The bundler injects the dependencies in the
\texttt{package.json} pertaining to the module to make them available at
runtime.
This section covers how to set up npm-based portlet projects.
\chapter{Formatting Your npm Modules for
AMD}\label{formatting-your-npm-modules-for-amd}
For Liferay DXP to recognize your npm modules, they must be formatted
for the Liferay AMD Loader. Luckily, the liferay-npm-bundler handles
this for you, you just have to provide the proper configuration and add
it to your build script. This article shows how to use the
liferay-npm-bundler to set up npm-based portlet projects.
Follow these steps to configure your project to use the
liferay-npm-bundler:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Install NodeJS \textgreater=
\href{http://nodejs.org/dist/v6.11.0/}{v6.11.0} if you don't have it
installed.
\item
Navigate to your portlet's project folder and initialize a
\texttt{package.json} file if it's not present yet.
If you don't have a portlet already, create an empty MVC portlet
project. For convenience, you can use
\href{/docs/7-2/reference/-/knowledge_base/r/installing-blade-cli}{Blade
CLI} to create an empty portlet with the
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-mvc-portlet-template}{mvc
portlet blade template}.
If you don't have a \texttt{package.json} file, you can run
\texttt{npm\ init\ -y} to create an empty one based on the project
directory's name.
\item
Run this command to install the liferay-npm-bundler:
\begin{verbatim}
npm install --save-dev liferay-npm-bundler
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Use npm from within your portlet project's root folder (where the
`package.json` file lives), as you normally do on a typical web project.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\item
Add the \texttt{liferay-npm-bundler} to your \texttt{package.json}'s
build script to pack the needed npm packages and transform them to
AMD:
\begin{verbatim}
"scripts": {
"build": "liferay-npm-bundler"
}
\end{verbatim}
\item
Configure your project for the bundler, using the
\texttt{.npmbundlerrc} file (create this file in your project's root
folder if it doesn't exist). See the
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-the-npmbundlerrcs-structure}{liferay-npm-bundler's
\texttt{.npmbundlerrc} structure reference} for more information on
the available options. Specify the
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-npm-bundlers-loaders}{loaders
and rules} to use for your project's source files. The example below
processes the JavaScript files in the project's \texttt{/src/} and
\texttt{/assets/} folders with Babel via the
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-babel-loader}{\texttt{babel-loader}}:
\begin{verbatim}
{
"sources": ["src", "assets"],
"rules": [
{
"test": "\\.js$",
"exclude": "node_modules",
"use": [
{
"loader": "babel-loader",
"options": {
"presets": ["env"]
}
}
]
}
]
}
\end{verbatim}
\item
Run \texttt{npm\ install} to install the required dependencies.
\item
Run the build script to bundle your dependencies with the
liferay-npm-bundler:
\begin{verbatim}
npm run-script build
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} By default, the AMD Loader times out in seven seconds.
You can configure this value through System Settings. Open the Control
Panel and navigate to \emph{Configuration} → \emph{System Settings} →
\emph{PLATFORM} → \emph{Infrastructure}, and select \emph{JavaScript
Loader}. Set the \emph{Module Definition Timeout} configuration to the
time you want and click \emph{Save}.
\noindent\hrulefill
Great! Now you know how to use the liferay-npm-bundler to bundle your
npm-based portlets for the Liferay AMD Loader.
\section{Related Topics}\label{related-topics-98}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-javascript-in-your-portlets}{Preparing
Your JavaScript Files for ES2015+}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-npm-bundlers-loaders}{Understanding
liferay-npm-bundler's Loaders and Rules}
\end{itemize}
\chapter{Migrating a liferay-npm-bundler Project from 1.x to
2.x}\label{migrating-a-liferay-npm-bundler-project-from-1.x-to-2.x}
You should use the latest 2.x version of the liferay-npm-bundler. It
\href{/docs/7-2/reference/-/knowledge_base/r/what-changed-between-liferay-npm-bundler-1-x-and-2-x}{offers
more stability and includes more features out-of-the-box}. If you
already created a project using the 1.x version, don't worry. Follow
these steps to migrate your project to 2.x:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Update the \texttt{liferay-npm-bundler} dependency in your
\texttt{package.json} to version 2.x:
\begin{verbatim}
{
"devDependencies": {
...
"liferay-npm-bundler": "^2.0.0",
...
},
...
}
\end{verbatim}
\item
Remove all \texttt{liferay-npm-bundler-preset-*} dependencies from
your \texttt{package.json} because liferay-npm-bundler 2.x includes
these by default.
\item
Remove any bundler presets you configured in your
\texttt{.npmbundlerrc} file. liferay-npm-bundler 2.x includes one
smart preset that handles all frameworks automatically.
\end{enumerate}
These are the standard requirements that all projects have in common.
The remaining steps depend on your project's framework. Follow the
instructions in the corresponding section to finish migrating your
project.
\chapter{Migrating a Plain JavaScript, Billboard JS, JQuery, Metal JS,
React, or Vue JS Project to Use Bundler
2.x}\label{migrating-a-plain-javascript-billboard-js-jquery-metal-js-react-or-vue-js-project-to-use-bundler-2.x}
After following the steps covered in the intro to this section, follow
these remaining steps to migrate the framework projects shown below to
2.x:
\begin{itemize}
\tightlist
\item
plain JS project
\item
Billboard.js project
\item
JQuery project
\item
Metal.js project
\item
React project
\item
Vue.js project
\end{itemize}
While Babel is required to transpile your source files, you must remove
any Babel preset used for transformations from your project that bundler
1.x imposed. liferay-npm-bundler 2.x handles these transformations by
default:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Remove the \emph{liferay-project} preset from your project's
\texttt{.babelrc} file. All that should remain is the \texttt{es2015}
preset shown below:
\begin{verbatim}
{
"presets": ["es2015"]
}
\end{verbatim}
If your project uses React, make sure the \texttt{react} preset
remains as well:
\begin{verbatim}
{
"presets": ["es2015", "react"]
}
\end{verbatim}
\item
Remove the \texttt{babel-preset-liferay-project} dependency from your
\texttt{package.json}.
\end{enumerate}
Awesome! Your project is migrated to use the new version of the
liferay-npm-bundler.
\section{Related Topics}\label{related-topics-99}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/formatting-your-npm-modules-for-amd}{Formatting
Your npm Modules for AMD}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-npmresolver-api-in-your-portlets}{Using
the NPMResolver API in Your Portlets}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/what-changed-between-liferay-npm-bundler-1-x-and-2-x}{What
Changed between liferay-npm-bundler 1.x and 2.x}
\end{itemize}
\chapter{Migrating an Angular Project to Use Bundler
2.x}\label{migrating-an-angular-project-to-use-bundler-2.x}
After following the steps covered in the intro to this section, follow
these remaining steps to migrate your Angular project to 2.x. While
liferay-npm-bundler 1.x relied on Babel to perform some transformation
steps, these transformations are now automatically applied in version
2.x. Therefore, you should remove Babel from your project:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your \texttt{tsconfig.json} file and replace the
\texttt{"module":\ "amd"} compiler option with the configuration shown
below to produce CommonJS modules:
\begin{verbatim}
{
"compilerOptions": {
...
"module": "commonjs",
...
}
}
\end{verbatim}
\item
Delete the \texttt{.babelrc} file to remove the Babel configuration.
\item
Remove Babel from your \texttt{package.json} build process so it
matches the configuration below:
\begin{verbatim}
{
"scripts": {
"build": "tsc && liferay-npm-bundler"
},
...
}
\end{verbatim}
\item
Remove the following Babel dependencies from your
\texttt{package.json} \emph{devDependencies}:
\begin{verbatim}
"babel-cli": "6.26.0",
"babel-preset-liferay-amd": "1.2.2"
\end{verbatim}
\end{enumerate}
Great! Your project is migrated to use the new version of the
liferay-npm-bundler.
\section{Related Topics}\label{related-topics-100}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/formatting-your-npm-modules-for-amd}{Formatting
Your npm Modules for AMD}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-the-npmresolver-api-in-your-portlets}{Using
the NPMResolver API in Your Portlets}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/what-changed-between-liferay-npm-bundler-1-x-and-2-x}{What
Changed between liferay-npm-bundler 1.x and 2.x}
\end{itemize}
\chapter{Migrating Your Project to Use liferay-npm-bundler's New
Mode}\label{migrating-your-project-to-use-liferay-npm-bundlers-new-mode}
In the previous version of the liferay-npm-bundler, before the bundler
ran, the build did some preprocessing, then the bundler modified the
output from the preprocessed files, as shown in the example build script
below:
\begin{verbatim}
{
"scripts":{
"build": "babel --source-maps -d build src && liferay-npm-bundler"
}
}
\end{verbatim}
In the new mode, the liferay-npm-bundler runs the whole process, like
webpack, and is configured via a set of rules. The build script is
condensed, as shown below:
\begin{verbatim}
{
"scripts":{
"build": "liferay-npm-bundler"
}
}
\end{verbatim}
Follow these steps to migrate your project to use the new configuration
mode:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the project's \texttt{package.json} file and update the
\texttt{build} script to use only the liferay-npm-bundler:
\begin{verbatim}
{
"scripts":{
"build": "liferay-npm-bundler"
}
}
\end{verbatim}
\item
Define the rules for the bundler to use (e.g.~running babel to
transpile files) in the project's \texttt{.npmbundlerrc} file. The
example configuration below defines rules for using the
\texttt{babel-loader} to transpile JavaScript files. See the
\href{/docs/7-2/reference/-/knowledge_base/r/default-liferay-npm-bundler-loaders}{Default
Loaders reference} for the full list of default loaders. Follow the
steps in
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-loaders-for-the-liferay-npm-bundler}{Creating
Custom Loaders for the Bundler} to create a custom loader. The
liferay-npm-bundler processes the \texttt{*.js} files in
\texttt{/src/} with babel and writes the results in the default
\texttt{/build/} folder:
\begin{verbatim}
{
"sources": ["src"],
"rules": [
{
"test": "\\.js$",
"exclude": "node_modules",
"use": [
{
"loader": "babel-loader",
"options": {
"presets": ["env"]
}
}
]
}
]
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** The new mode of the liferay-npm-bundler acts very much
like webpack, but because webpack creates a single JS bundle file and
liferay-npm-bundler targets AMD loader, they are not compatible.
\end{verbatim}
\noindent\hrulefill
\section{Related Topics}\label{related-topics-101}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/default-liferay-npm-bundler-loaders}{Default
liferay-npm-bundler Loaders}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-npm-bundlers-loaders}{Understanding
liferay-npm-bundler's Loaders}
\end{itemize}
\chapter{Creating Custom Loaders for the
liferay-npm-bundler}\label{creating-custom-loaders-for-the-liferay-npm-bundler}
Since webpack creates JavaScript bundles and the liferay-npm-bundler
targets AMD loader, webpack's loaders aren't compatible with the
liferay-npm-bundler. So, if you want to use a loader that isn't
\href{/docs/7-2/reference/-/knowledge_base/r/default-liferay-npm-bundler-loaders}{available
by default}, you must create a custom loader.
A loader, in terms of the liferay-npm-bundler, is defined as an npm
package that has a main module which exports a default function with
this signature:
\begin{verbatim}
function(context, options){
}
\end{verbatim}
The arguments are defined as follows:
\texttt{context}: an object containing these fields:
\begin{quote}
\texttt{content}: a string with the contents of the processed file (the
main input of the loader)
\end{quote}
\begin{quote}
\texttt{filepath}: the project-relative path to the file to process with
the loader
\end{quote}
\begin{quote}
\texttt{extraArtifacts}: an object with project-relative paths as keys
and strings as values of properties that may be used to output extra
files along with the one being processed (for example, you can use it to
generate source maps).
\end{quote}
\begin{quote}
\texttt{log}: a logger that writes execution information to the
bundler's report file (see the
\href{https://github.com/liferay/liferay-js-toolkit/blob/master/packages/liferay-npm-build-tools-common/src/plugin-logger.js}{\texttt{PluginLogger}
class} for information on its structure and API).
\end{quote}
\texttt{options}: an object taken from the \texttt{options} field of the
loader's configuration (See
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-npm-bundlers-loaders}{Understanding
liferay-npm-bundler's loaders and rules} for more information).
\noindent\hrulefill
\textbf{Note:} the function may return nothing or modified content. If
something is returned, it is copied on top of the
\texttt{context.content} field and used to feed the next loader or write
the output file. This is the equivalent to
\texttt{context.content\ =\ \textquotesingle{}something\textquotesingle{}}.
If your loader does not return a file, but instead it only filters files
to prevent them from being generated, you must explicitly set
\texttt{context.content\ =\ \textquotesingle{}undefined\textquotesingle{}}.
\noindent\hrulefill
Follow these steps to write a new loader. These steps use the Babel
loader as an example:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
If your loader requires configuration, like Babel, you may define a
rule configuration like the one shown below so you can specify options
for the loader:
\begin{verbatim}
{
"rules": [
{
"test": "\\.js$",
"exclude": "node_modules",
"use": [
{
"loader": "babel-loader",
"options": {
"presets": ["env", "react"]
}
}
]
}
]
}
\end{verbatim}
\item
Create an \texttt{index.js} file and write a function that takes the
input content, passes it through the loader, and writes the result and
the source map file to the output folder. The loader function below
takes the passed content (JS files), run it through babel, and writes
the result and source map to the default \texttt{/build/} output
folder:
\begin{verbatim}
export default function(context, options) {
// Get input parameters
const { content, filePath, log, sourceMap } = context;
// Run babel on content
const result = babel.transform(content, options);
// Create an extra .map file with source map next to source .js file
context.extraArtifacts[`${filePath}.map`] = JSON.stringify(result.map);
// Tell the user what we have done
log.info("babel-loader", "Transpiled file");
// Return the modified content
return result.code;
}
\end{verbatim}
\item
Place the \texttt{index.js} file in an npm package and publish it.
\item
Include the npm package you just created as a \texttt{devDependency}
in the project's \texttt{package.json}:
\begin{verbatim}
"devDependencies": {
"liferay-npm-bundler": "2.12.0",
"liferay-npm-build-support": "2.12.0",
"liferay-npm-bundler-loader-babel-loader": "2.12.0",
...
}
\end{verbatim}
\item
Configure the loader's name in the \texttt{rules} section of the
project's \texttt{.npmbundlerrc} file:
\begin{verbatim}
{
"sources": ["src"],
...
"rules": [
{
"test": "\\.js$",
"exclude": "node_modules",
"use": [
{
"loader": "babel-loader",
"options": {
"presets": ["env", "react"]
}
}
]
}
],
...
}
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-102}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/default-liferay-npm-bundler-loaders}{Default
liferay-npm-bundler Loaders}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-npm-bundlers-loaders}{Understanding
liferay-npm-bundler's Loaders}
\end{itemize}
\chapter{Using the NPMResolver API in Your
Portlets}\label{using-the-npmresolver-api-in-your-portlets}
If you're developing an npm-based portlet, your OSGi bundle's
\texttt{package.json} is a treasure-trove of information. It contains
everything that's stored in the npm registry about your bundle: default
entry point, dependencies, modules, package names, versions, and more.
The
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/NPMResolver.html}{\texttt{NPMResolver}
APIs} expose this information so you can access it in your portlet. If
it's defined in the OSGi bundle's \texttt{package.json}, you can
retrieve the information in your portlet with the \texttt{NPMResolver}
API. For instance, you can use this API to
\href{/docs/7-2/frameworks/-/knowledge_base/f/obtaining-dependency-npm-package-descriptors}{reference
an npm package's static resources} (such as CSS files) and even to
\href{/docs/7-2/frameworks/-/knowledge_base/f/referencing-an-npm-modules-package}{make
your code more maintainable}.
To enable the \texttt{NPMResolver} in your portlet, use the
\texttt{@Reference} annotation to inject the \texttt{NPMResolver} OSGi
component into your portlet's Component class, as shown below:
\begin{verbatim}
import com.liferay.frontend.js.loader.modules.extender.npm.NPMResolver;
public class MyPortlet extends MVCPortlet {
@Reference
private NPMResolver `_npmResolver`;
}
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} Because the \texttt{NPMResolver} reference is tied
directly to the OSGi bundle's \texttt{package.json} file, it can only be
used to retrieve npm module and package information from that file. You
can't use the \texttt{NPMResolver} to retrieve npm package information
for other OSGi bundles.
\noindent\hrulefill
Now that the \texttt{NPMResolver} is added to your portlet, read the
topics in this section to learn how to retrieve your OSGi bundle's npm
package and module information.
\chapter{Referencing an npm Module's Package to Improve Code
Maintenance}\label{referencing-an-npm-modules-package-to-improve-code-maintenance}
Once you've
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-javascript-in-your-portlets}{exposed
your modules}, you can use them in your portlet via the
\texttt{aui:script} tag's \texttt{require} attribute. By default,
Liferay DXP automatically composes an npm module's JavaScript variable
based on its name. For example, the module \texttt{my-package@1.0.0}
translates to the variable \texttt{myPackage100} for the
\texttt{\textless{}aui:script\textgreater{}} tag's \texttt{require}
attribute. This means that each time a new version of the OSGi bundle's
npm package is released, you must update your code's variable to reflect
the new version. You can use the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/JSPackage.html}{\texttt{JSPackage}
interface} to obtain the module's package name and create an alias to
reference it, so the variable name always reflects the latest version
number!
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Retrieve a reference to the OSGi bundle's npm package using the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/NPMResolver.html\#getJSPackage}{\texttt{getJSPackage()}
method}:
\begin{verbatim}
JSPackage jsPackage = _npmResolver.getJSPackage();
\end{verbatim}
\item
Grab the npm package's resolved ID (the current package version, in
the format
\texttt{\textless{}package\ name\textgreater{}@\textless{}version\textgreater{}},
defined in the OSGi module's \texttt{package.json}) using the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/JSPackage.html\#getResolvedId}{\texttt{getResolvedId()}
method} and alias it with the \texttt{as\ myVariableName} pattern. The
example below retrieves the npm module's resolved ID, sets it to the
\texttt{bootstrapRequire} variable, and assigns the entire value to
the attribute \texttt{bootstrapRequire}. This ensures that the package
version is always up to date:
\begin{verbatim}
renderRequest.setAttribute(
"bootstrapRequire",
jsPackage.getResolvedId() + " as bootstrapRequire");
\end{verbatim}
\item
Include the reference to the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/NPMResolver.html}{\texttt{NPMResolver}}:
\begin{verbatim}
@Reference
private NPMResolver _npmResolver;
\end{verbatim}
\item
Resolve the \texttt{JSPackage} and \texttt{NPMResolver} imports:
\begin{verbatim}
import com.liferay.frontend.js.loader.modules.extender.npm.JSPackage;
import com.liferay.frontend.js.loader.modules.extender.npm.NPMResolver;
\end{verbatim}
\item
In the portlet's JSP, retrieve the aliased attribute
(\texttt{bootstrapRequire} in the example):
\begin{verbatim}
<%
String bootstrapRequire =
(String)renderRequest.getAttribute("bootstrapRequire");
%>
\end{verbatim}
\item
Finally, use the attribute as the
\texttt{\textless{}aui:script\textgreater{}} require attribute's
value:
\begin{verbatim}
bootstrapRequire.default();
\end{verbatim}
Below is the full example \texttt{*Portlet} class:
\begin{verbatim}
public class MyPortlet extends MVCPortlet {
@Override
public void doView(
RenderRequest renderRequest, RenderResponse renderResponse)
throws IOException, PortletException {
JSPackage jsPackage = _npmResolver.getJSPackage();
renderRequest.setAttribute(
"bootstrapRequire",
jsPackage.getResolvedId() + " as bootstrapRequire");
super.doView(renderRequest, renderResponse);
}
@Reference
private NPMResolver _npmResolver;
}
\end{verbatim}
And here is the corresponding example \texttt{view.jsp}:
\begin{verbatim}
<%
String bootstrapRequire =
(String)renderRequest.getAttribute("bootstrapRequire");
%>
bootstrapRequire.default();
\end{verbatim}
\end{enumerate}
Now you know how to reference an npm module's package!
\section{Related Topics}\label{related-topics-103}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/obtaining-dependency-npm-package-descriptors}{Obtaining
an OSGi bundle's Dependency npm Package Descriptors}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-npm-bundler}{liferay-npm-bundler}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/how-the-liferay-npm-bundler-publishes-npm-packages}{How
Liferay DXP Publishes npm Packages}
\end{itemize}
\chapter{Obtaining an OSGi bundle's Dependency npm Package
Descriptors}\label{obtaining-an-osgi-bundles-dependency-npm-package-descriptors}
While writing your npm portlet, you may need to reference a dependency
package or its modules. For instance, you can retrieve an npm dependency
package module's CSS file and use it in your portlet. The
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/NPMResolver.html}{\texttt{NPMResolver}
OSGi component} provides two methods for retrieving an OSGi bundle's
dependency npm package descriptors:
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/NPMResolver.html\#getDependencyJSPackage}{\texttt{getDependencyJSPackage()}}
to retrieve dependency npm packages and
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/frontend/js/loader/modules/extender/npm/NPMResolver.html\#resolveModuleName}{\texttt{resolveModuleName()}}
to retrieve dependency npm modules. This article references the
\texttt{package.json} below to help demonstrate these methods:
\begin{verbatim}
{
"dependencies": {
"react": "15.6.2",
"react-dom": "15.6.2"
},
.
.
.
}
\end{verbatim}
To obtain an OSGi bundle's npm dependency package, pass the package's
name in as the \texttt{getDependencyJSPackage()} method's argument. The
example below resolves the \texttt{react} dependency package:
\begin{verbatim}
String reactResolvedId = npmResolver.getDependencyJSPackage("react");
\end{verbatim}
\texttt{reactResolvedId}'s resulting value is \texttt{react@15.6.2}.
You can use the \texttt{resolveModuleName()} method To obtain a module
in an npm dependency package. To do this, pass the module's relative
path in as the \texttt{resolveModuleName()} method's argument. The
example below resolves a module named \texttt{react-with-addons} for the
\texttt{react} dependency package:
\begin{verbatim}
String resolvedModule =
npmResolver.resolveModuleName("react/dist/react-with-addons");
\end{verbatim}
The \texttt{resolvedModule} variable evaluates to
\texttt{react@15.6.2/dist/react-with-addons}. You can also use this to
reference static resources inside npm packages (like CSS or image
files), as shown in the example below:
\begin{verbatim}
String cssPath = npmResolver.resolveModuleName(
"react/lib/css/main.css");
\end{verbatim}
Now you know how to obtain an OSGi bundle's dependency npm packages
descriptors!
\section{Related Topics}\label{related-topics-104}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/referencing-an-npm-modules-package}{Referencing
an npm Module's Package}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/the-structure-of-osgi-bundles-containing-npm-packages}{The
Structure of OSGi Bundles Containing npm Packages}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/how-the-liferay-npm-bundler-publishes-npm-packages}{How
Liferay DXP Publishes npm Packages}
\end{itemize}
\chapter{Automatic Single Page
Applications}\label{automatic-single-page-applications}
A good user experience is the measure of a well-designed site. A user's
time is highly valuable. The last thing you want is for someone to grow
frustrated with your site because of constant page reloads. A Single
Page Application (SPA) avoids this issue. Single Page Applications
drastically cut down on load times by loading only a single HTML page
that's dynamically updated as the user interacts and navigates through
the site. This provides a more seamless app experience by eliminating
page reloads. \textbf{SPA is enabled by default in your apps and sites
and requires no changes to your workflow or code!} If Spa is disabled,
ensure that the \texttt{com.liferay.frontend.js.spa.web-{[}version{]}}
module is deployed and active.
\section{The Benefits of SPAs}\label{the-benefits-of-spas}
Let's say you're surfing the web and you find a really rad site that
happens to be SPA enabled. All right! Page load times are blazin' fast.
You're deep into the site, scrolling along, when you find this great
post that just speaks to you. You copy the URL from the address bar and
email it to all of your friends with the subject: `Your Life Will Change
Forever.' They must experience this awe-inspiring work!
You get a response back almost immediately. ``This is a rad site, but
what post are you talking about?'' it reads.
``What!? Do my eyes deceive me?'' you exclaim. You were in so much of a
hurry to share this life-changing content that you neglected to notice
that the URL never updated when you clicked the post. You click the back
button, hoping to get back to the post, but it takes you to the site you
were on before you ever visited this one. The page history didn't update
as you navigated through the app; Only the main app URL was saved.
What a bummer! ``Why? Why have you failed me, site?'' you cry.
If only there was a way to have a Single Page Application, but also be
able to link to the content you want. Well, don't despair my friend. You
can have your cake and eat it too, thanks to SennaJS.
\section{What is SennaJS?}\label{what-is-sennajs}
SennaJS is Liferay DXP's SPA engine. SennaJS handles the client-side
data, and AJAX loads the page's content dynamically. While there are
other JavaScript frameworks out there that may provide some of the same
features, Senna's only focus is SPA, ensuring that your site provides
the best user experience possible.
SennaJS provides the following key enhancements to SPA:
\textbf{SEO \& Bookmarkability}: Sharing or bookmarking a link displays
the same content you are viewing. Search engines are able to index this
content.
\textbf{Hybrid rendering}: Ajax + server-side rendering lets you disable
\texttt{pushState} at any time, allowing progressive enhancement. You
can use your preferred method to render the server side (e.g.~HTML
fragments or template views).
\textbf{State retention}: Scrolling, reloading, or navigating through
the history of the page takes you back to where you were. SennaJS
exposes lifecycle events that represent state changes in the
application. See \href{available-spa-lifecycle-events}{Available SPA
Lifecycle Events} for more information.
\textbf{UI feedback}: The UI indicates to the user when some content is
requested.
\textbf{Pending navigations}: UI rendering is blocked until data is
loaded, and the content is displayed all at once.
\textbf{Timeout detection}: If the request takes too long to load or the
user tries to navigate to a different link while another request is
pending, the request times out.
\textbf{History navigation}: The browser history is manipulated via the
\href{https://developer.mozilla.org/en-US/docs/Web/API/History}{History
API}, so you can use the back and forward history buttons to navigate
through the history of the page.
\textbf{Cacheable screens}: Once a surface is loaded, the content is
cached in memory and is retrieved later without any additional request,
speeding up your application.
\textbf{Page resources management}: Scripts and stylesheets are
evaluated from dynamically loaded resources. Additional content can be
appended to the DOM using \texttt{XMLHttpRequest}. For security reasons,
some browsers won't evaluate \texttt{\textless{}script\textgreater{}}
tags from content fragments. Therefore, SennaJS extracts scripts from
the content and parses them to ensure that they meet the browser loading
requirements.
You can see examples and read more about SennaJS at its
\href{http://sennajs.com/}{website}.
This section covers these topics:
\begin{itemize}
\tightlist
\item
Configuring SPA System Settings
\item
Disabling SPA
\item
Specifying how resources are loaded during SPA navigation
\item
Detaching Global Listeners
\end{itemize}
\chapter{Configuring SPA System
Settings}\label{configuring-spa-system-settings}
Depending on what behaviors you need to customize, you can configure SPA
options in one of two places. SPA caching and SPA timeout settings are
configured in System Settings. If you wish to disable SPA for a certain
link, page, or portlet in your site, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/disabling-spa}{Disabling
SPA}.
Follow these steps to configure system settings for SPA:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the Control Panel, navigate to \emph{Configuration} → \emph{System
Settings}.
\item
Select \emph{Infrastructure} under the \emph{PLATFORM} heading.
\item
Click \emph{Frontend SPA Infrastructure}.
\end{enumerate}
The following configuration options are available:
\textbf{Cache Expiration Time}: The time, in minutes, in which the SPA
cache is cleared. A negative value means the cache should be disabled.
\textbf{Navigation Exception Selectors}: Defines a CSS selector that SPA
should ignore.
\textbf{Request Timeout Time}: The time, in milliseconds, in which a SPA
request times out. A zero value means the request should never timeout.
\textbf{User Notification Timeout}: The time, in milliseconds, in which
a notification is shown to the user stating that the request is taking
longer than expected. A zero value means no notification should be
shown.
Great! Now you know how to configure system settings for SPA.
\section{Related Topics}\label{related-topics-105}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/disabling-spa}{Disabling
SPA}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-how-resources-are-loaded-during-navigation}{Specifying
How Resources Are Loaded During SPA Navigation}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/detaching-global-listeners}{Detaching
Global Listeners}
\end{itemize}
\chapter{Disabling SPA}\label{disabling-spa}
Certain elements of your page may require a regular navigation to work
properly. For example, you may have downloadable content that you want
to share with the user. In these cases, SPA must be disabled for those
specific elements. You can disable SPA at these scopes:
\begin{itemize}
\tightlist
\item
disable SPA across an entire Liferay DXP instance
\item
disable SPA in a portlet
\item
Disable SPA in individual elements
\end{itemize}
Follow the steps in the corresponding section to disable SPA for that
scope.
\section{Disabling SPA across an
Instance}\label{disabling-spa-across-an-instance}
To disable SPA across an entire Liferay DXP instance, add the following
line to your \texttt{portal-ext.properties}:
\begin{verbatim}
javascript.single.page.application.enabled = false
\end{verbatim}
\section{Disabling SPA for a Portlet}\label{disabling-spa-for-a-portlet}
To disable SPA for a portlet, you must blacklist it. To blacklist a
portlet from SPA, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your portlet class.
\item
Set the \texttt{com.liferay.portlet.single-page-application} property
to false:
\begin{verbatim}
com.liferay.portlet.single-page-application=false
\end{verbatim}
If you prefer, you can set this property to false in your
\texttt{liferay-portlet.xml} instead by adding the following property
to the \texttt{\textless{}portlet\textgreater{}} section:
\begin{verbatim}
false
\end{verbatim}
\item
Alternatively, you can override the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/model/impl/PortletImpl.html\#isSinglePageApplication--}{\texttt{isSinglePageApplication}
method} of the portlet to return \texttt{false}.
\end{enumerate}
\section{Disabling SPA for an Individual
Element}\label{disabling-spa-for-an-individual-element}
To disable SPA for a form or link follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{data-senna-off} attribute to the element.
\item
Set the value to \texttt{true}. See the example below:
\begin{verbatim}
Page 2
\end{verbatim}
\end{enumerate}
Nice! Now you know how to disable SPA in your app.
\section{Related Topics}\label{related-topics-106}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-spa-system-settings}{Configuring
SPA System Settings}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-how-resources-are-loaded-during-navigation}{Specifying
How Resources Are Loaded During SPA Navigation}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/detaching-global-listeners}{Detaching
Global Listeners}
\end{itemize}
\chapter{Specifying How Resources Are Loaded During
Navigation}\label{specifying-how-resources-are-loaded-during-navigation}
By default, Liferay DXP unloads CSS resources from the
\texttt{\textless{}head\textgreater{}} element on navigation. JavaScript
resources in the \texttt{\textless{}head\textgreater{}}, however, are
not removed on navigation. This functionality can be customized by
setting the resource's \texttt{data-senna-track} attribute. Follow these
steps to customize your resources:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select the resource you want to modify the default behavior for.
\item
Add the \texttt{data-senna-track} attribute to the resource.
\item
Set the \texttt{data-senna-track} attribute to \texttt{permanent} to
prevent a resource from unloading on navigation.
Alternatively, set the \texttt{data-senna-track} attribute to
\texttt{temporary} to unload the resource on navigation.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** the `data-senna-track` attribute can be added to resources loaded
outside of the `` element as well to specify navigation behavior.
\end{verbatim}
\noindent\hrulefill
The example below ensures that the JS resource isn't unloaded during
navigation:
\begin{verbatim}
\end{verbatim}
Great! Now you know how to specify how resources are loaded during SPA
navigation.
\section{Related Topics}\label{related-topics-107}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-spa-system-settings}{Configuring
SPA System Settings}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/disabling-spa}{Disabling
SPA}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/detaching-global-listeners}{Detaching
Global Listeners}
\end{itemize}
\chapter{Detaching Global Listeners}\label{detaching-global-listeners}
SPA provides several improvements that highly benefit your site and
users, but with great power comes great re-SPA-nsibility. I apologize
for that last sentence, but the fact remains that there is potentially
some additional maintenance as a consequence of SPA. In a traditional
navigation scenario, every page refresh resets everything, so you don't
have to worry about what's left behind. In a SPA scenario, however,
global listeners such as \texttt{Liferay.on}, \texttt{Liferay.after}, or
body delegates can become problematic. Every time you execute these
global listeners, you add yet another listener to the globally persisted
\texttt{Liferay} object. The result is multiple invocations of those
listeners. This can obviously cause problems if not handled.
Follow these steps to prevent potential problems:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Listen to the navigation event in order to detach your listeners. For
example, you would use the following code to listen to a global
\texttt{category} event and \texttt{destroyPortlet} event:
\begin{verbatim}
Liferay.on('category', function(event){...});
Liferay.on('destroyPortlet', function(event){...});
\end{verbatim}
\item
Detach the event listeners when the portlet is destroyed, as shown in
the example below:
\begin{verbatim}
var onCategory = function(event) {...};
var clearPortletHandlers = function(event) {
if (event.portletId === '<%= portletDisplay.getRootPortletId() %>') {
Liferay.detach('onCategoryHandler', onCategory);
Liferay.detach('destroyPortlet', clearPortletHandlers);
}
};
Liferay.on('category', onCategory);
Liferay.on('destroyPortlet', clearPortletHandlers);
\end{verbatim}
\end{enumerate}
Great! Now you know how to properly maintain your global listeners for
SPA.
\section{Related Topics}\label{related-topics-108}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/available-spa-lifecycle-events}{Available
SPA Lifecycle Events}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/disabling-spa}{Disabling
SPA}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-spa-system-settings}{Configuring
SPA System Settings}
\end{itemize}
\chapter{Applying Clay Styles to your
App}\label{applying-clay-styles-to-your-app}
It's important to have a consistent user experience across your apps.
Portal's built-in apps achieve this through Liferay's
\href{https://liferay.design/lexicon/}{Lexicon Experience Language} and
its web implementation,
\href{https://clayui.com/docs/getting-started/clay.html}{Clay}.
Clay provides a consistent, user-friendly UI and is included in all
themes that are based on the \texttt{\_styled} base theme, making all
the components documented on the
\href{https://clayui.com/docs/components/alerts.html}{Clay site}
accessible.
This means you can use Clay markup and components in your apps. This
section explains how to apply Clay's design patterns to achieve the same
look and feel as Portal's built-in apps.
This section covers these topics:
\begin{itemize}
\tightlist
\item
Applying Clay to navigation
\item
Implementing the Management Toolbar
\end{itemize}
\chapter{Applying Clay Patterns to
Navigation}\label{applying-clay-patterns-to-navigation}
This article covers how to leverage Clay patterns in your app's
navigation to make it more user-friendly. Updating your app's navigation
bar to use Clay is easy, thanks to the
\texttt{\textless{}clay:navigation-bar\ /\textgreater{}} tag. Follow
these steps to update your app:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the required imports to your app's \texttt{init.jsp}:
\begin{verbatim}
// Import the clay tld file to be able to use the new tag
<%@ taglib uri="http://liferay.com/tld/clay" prefix="clay" %>
// Import the NavigationItem utility class to create the items model
<%@ page import="com.liferay.frontend.taglib.clay.servlet.taglib.util.JSPNavigationItemList" %>
\end{verbatim}
\item
Add the \texttt{frontend-taglib-clay} and \texttt{frontend.taglib.soy}
module dependencies to your app's \texttt{build.gradle} file:
\begin{verbatim}
compileOnly group: "com.liferay", name: "com.liferay.frontend.taglib.soy",
version: "1.0.10"
compileOnly group: "com.liferay", name: "com.liferay.frontend.taglib.clay",
version: "1.0.0"
\end{verbatim}
\item
Inside your JSP view, add a java scriplet to retrieve the navigation
variable and portlet URL. An example configuration is shown below:
\begin{verbatim}
<%
final String navigation = ParamUtil.getString(request, "navigation",
"entries");
PortletURL portletURL = renderResponse.createRenderURL();
portletURL.setParameter("mvcRenderCommandName", "/blogs/view");
portletURL.setParameter("navigation", navigation);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}clay:navigation-bar\ /\textgreater{}} tag
to your app, and use the \texttt{items} attribute to specify the
navigation items. The navigation bar should be dark if your app is
intended for Admin use. To do this, set the \texttt{inverted}
attribute to \texttt{true}. If your app is intended for an instance on
a live site, keep the navigation bar light by setting the
\texttt{inverted} attribute to \texttt{false}. An example
configuration for an admin app is shown below:
\begin{verbatim}
{
navigationItem.setActive(navigation.equals("images"));
navigationItem.setHref(renderResponse.createRenderURL(),
"navigation", "images");
navigationItem.setLabel(LanguageUtil.get(request, "images"));
});
}
}
%>"
/>
\end{verbatim}
\item
Add a conditional block to display the proper JSP for the selected
navigation item. An example configuration for the Blogs Admin portlet
is shown below:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Live site navigation bar:
\begin{figure}
\centering
\includegraphics{./images/clay-patterns-navbar.png}
\caption{The navigation bar should be light for apps on the live site.}
\end{figure}
Admin app navigation bar:
\begin{figure}
\centering
\includegraphics{./images/clay-patterns-navbar-inverted.png}
\caption{The navigation bar should be dark (inverted) in admin apps.}
\end{figure}
Sweet! Now you know how to style a navigation bar with Clay.
\section{Related topics}\label{related-topics-109}
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-the-management-toolbar}{Implementing
the Management Toolbar}
\chapter{Implementing the Management
Toolbar}\label{implementing-the-management-toolbar}
The Management Toolbar is a combination of search, filters, sorting
options, and display options that let you manage data. For admin apps,
we recommend that you add a management toolbar to manage your search
container results. The
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Clay
Management Toolbar} taglib reference covers how to use the Clay taglibs
to create the Management Toolbar. This section covers how to create the
features below for the Management Toolbar:
\begin{itemize}
\tightlist
\item
Implementing View Types
\item
Sorting and Filtering Items
\end{itemize}
\chapter{Implementing the View Types}\label{implementing-the-view-types}
The Management Toolbar has three predefined view types for your app's
search container results. Each style offers a slightly different look
and feel. To provide these view types in your app, you must make some
updates to your search result columns. Start by defining the view types
you want to provide.
\begin{itemize}
\item
\textbf{Cards:} displays search result columns on a horizontal or
vertical card
\item
\textbf{List:} displays a detailed description along with summarized
details for the search result columns
\item
\textbf{Table:} the default view, which list the search result columns
from left to right
\end{itemize}
Follow these steps to define the view types for your management toolbar:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the \texttt{ViewTypeItemList} utility class to create the
action items model:
\begin{verbatim}
<%@ page import="com.liferay.frontend.taglib.clay.servlet.taglib.util.JSPViewTypeItemList" %>
\end{verbatim}
\item
Add the \texttt{frontend.taglib.clay} and \texttt{frontend.taglib.soy}
module dependencies to your app's \texttt{build.gradle} file:
\begin{verbatim}
compileOnly group: "com.liferay", name: "com.liferay.frontend.taglib.soy",
version: "1.0.10"
compileOnly group: "com.liferay", name: "com.liferay.frontend.taglib.clay",
version: "1.0.0"
\end{verbatim}
\item
In your app's main view, retrieve the \texttt{displayStyle} for
reference. Each view type corresponds to a display style. this is used
to determine the proper content configuration to display for the
selected view type:
\begin{verbatim}
<%
String displayStyle = ParamUtil.getString(request, "displayStyle");
%>
\end{verbatim}
\item
Add the management toolbar to your app's main view and configure the
display buttons as shown below. Note that while this example
implements all three view types, only one view type is required. The
default or active view type is set by adding
\texttt{viewTypeItem.setActive(true)} to the view type:
\begin{verbatim}
namespace="<%= renderResponse.getNamespace() %>"
searchContainerId="assetTags"
selectable="<%= true %>"
viewTypeItems="<%=
new JSPViewTypeItemList(pageContext, baseURL, selectedType) {
{
addCardViewTypeItem(
viewTypeItem -> {
viewTypeItem.setActive(true);
viewTypeItem.setLabel("Card");
});
addListViewTypeItem(
viewTypeItem -> {
viewTypeItem.setLabel("List");
});
addTableViewTypeItem(
viewTypeItem -> {
viewTypeItem.setLabel("Table");
});
}
}
%>"
/>
\end{verbatim}
\texttt{viewTypeItems}: The available view types
\item
Create a conditional block to check for the view types. If you only
have one view type, you can skip this step.
\begin{verbatim}
<%-- view type configuration goes here --%>
\end{verbatim}
\end{enumerate}
Now that the view types are defined, you can follow the instructions in
the rest of this section to configure them.
\chapter{Implementing the Card View}\label{implementing-the-card-view}
The card view displays the entry's information in a vertical or
horizontal card with an image, user profile, or an icon representing the
content's type, along with details about the content, such as its name,
workflow status, and a condensed description.
See the
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-cards}{Liferay
Frontend Cards} reference for examples and use cases of each card.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-view-type-card.png}
\caption{The Management Toolbar's card view gives a quick summary of the
content's description and status.}
\end{figure}
Follow the steps below to create your card view:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the \texttt{\textless{}c:choose\textgreater{}} conditional
block, add a condition for the icon display style (Card view type):
\begin{verbatim}
<%-- card view type configuration goes here --%>
\end{verbatim}
\item
Add the proper java scriplet to make the card view responsive to
different devices:
Use the pattern below for vertical cards:
\begin{verbatim}
<%
row.setCssClass("col-md-2 col-sm-4 col-xs-6");
%>
\end{verbatim}
For horizontal cards use the following pattern:
\begin{verbatim}
<%
row.setCssClass("col-md-3 col-sm-4 col-xs-12");
%>
\end{verbatim}
\item
Add the search container column text containing your card. The card
should include the actions for the entry(if applicable), as well as an
image, icon or user profile, and the entry's title. An example
configuration is shown below:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Great! Now you know how to implement the Card view!
\section{Related Topics}\label{related-topics-110}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Configuring
the Clay Management Toolbar Taglib}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/filtering-and-sorting-items-with-the-management-toolbar}{Filtering
and Sorting Items with the Management Toolbar}
\end{itemize}
\chapter{Implementing the List View}\label{implementing-the-list-view}
The list view displays the entry's complete description, along with a
small icon for the content type, and its name.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-view-type-list.png}
\caption{The Management Toolbar's list view gives the content's full
description.}
\end{figure}
Follow these steps to configure the List view:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the \texttt{\textless{}c:choose\textgreater{}} conditional
block, add a condition for the descriptive display style (list view
type):
\begin{verbatim}
<%-- list view type configuration goes here --%>
\end{verbatim}
\item
Follow the example below to add the three columns to the conditional
block:
\end{enumerate}
Column \textbar{} Content Options \textbar{} Example 1 \textbar{} icon
\textbar{}
\textless liferay-ui:search-container-column-icon/\textgreater{} ~
\textbar{} image \textbar{}
\textless liferay-ui:search-container-column-image/\textgreater{} ~
\textbar{} user portrait \textbar{}
\textless liferay-ui:search-container-column-text\textgreater{}~~\textless liferay-ui:user-portrait/\textgreater{}\textless/liferay-ui:search-container-column-text\textgreater{}
2 \textbar{} description \textbar{}
\textless liferay-ui:search-container-column-text
~~colspan=``\textless\%=2\%\textgreater{}''
\textgreater{}~~\textless h5\textgreater\textless\%= userGroup.getName()
\%\textgreater\textless/h5\textgreater{} ~~\textless h6
class=``text-default''\textgreater{}
~~~~\textless span\textgreater\textless\%= userGroup.getDescription()
\%\textgreater\textless/span\textgreater{} ~~\textless/h6\textgreater{}
~~\textless h6 class=``text-default''\textgreater{}
~~~~\textless span\textgreater{} ~~~~~~~~''
key=``x-users''/\textgreater{} ~~~~\textless/span\textgreater{}
~~\textless/h6\textgreater{} 3 \textbar{} actions \textbar{}
\textless liferay-ui:search-container-column-jsp
~~path=``/edit\_team\_assignments\_user\_groups\_action.jsp''/\textgreater{}
Great! Now you know how to implement the List view!
\section{Related Topics}\label{related-topics-111}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Configuring
the Clay Management Toolbar Taglib}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/filtering-and-sorting-items-with-the-management-toolbar}{Filtering
and Sorting Items with the Management Toolbar}
\end{itemize}
\chapter{Implementing the Table View}\label{implementing-the-table-view}
The table view list the search container columns from left to right.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-view-type-table.png}
\caption{The Management Toolbar's table view list the content's
information in individual columns.}
\end{figure}
Follow these steps to configure the Table view:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the \texttt{\textless{}c:choose\textgreater{}} conditional
block, add a condition for the list display style (table view type):
\begin{verbatim}
<%-- table view type configuration goes here --%>
\end{verbatim}
\item
Follow the example below to add the required columns to the
conditional block:
\end{enumerate}
Column \textbar{} Content Options \textbar{} Example 1 \textbar{} name
\textbar{} \textless liferay-ui:search-container-column-text
~~cssClass=``content-column name-column title-column'' ~~name=``name''
~~truncate=``\textless\%= true \%\textgreater{}'' ~~value=``\textless\%=
rule.getName(locale) \%\textgreater{}'' /\textgreater{} 2 \textbar{}
description \textbar{} \textless liferay-ui:search-container-column-text
~~cssClass=``content-column description-column'' ~~name=``description''
~~truncate=``\textless\%= true \%\textgreater{}'' ~~value=``\textless\%=
rule.getDescription(locale) \%\textgreater{}'' /\textgreater{} 3
\textbar{} create date \textbar{}
\textless liferay-ui:search-container-column-date
~~cssClass=``create-date-column text-column'' ~~name=``create-date''
~~property=``createDate'' /\textgreater{} 4 \textbar{} actions
\textbar{} \textless liferay-ui:search-container-column-jsp ~~
cssClass=``entry-action-column'' ~~path=``/rule\_actions.jsp''
/\textgreater{}
Great! Now you know how to implement the Table view!
\section{Related Topics}\label{related-topics-112}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Configuring
the Clay Management Toolbar Taglib}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/filtering-and-sorting-items-with-the-management-toolbar}{Filtering
and Sorting Items with the Management Toolbar}
\end{itemize}
\chapter{Updating the Search
Iterator}\label{updating-the-search-iterator}
Once the view type's display styles are defined, you must update the
search iterator to show the selected view type. Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
If your management toolbar only has one view type, you can set the
\texttt{displayStyle} attribute to the style you defined, otherwise
follow the pattern below:
\begin{verbatim}
\end{verbatim}
The \texttt{displayStyle}'s value is set to the
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-the-view-types}{current
view type}.
\item
Save the changes.
\end{enumerate}
Great! You've updated your search iterator to use your display styles.
\section{Related Topics}\label{related-topics-113}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Configuring
the Clay Management Toolbar Taglib}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/filtering-and-sorting-items-with-the-management-toolbar}{Filtering
and Sorting Items with the Management Toolbar}
\end{itemize}
\chapter{Filtering and Sorting Items with the Management
Toolbar}\label{filtering-and-sorting-items-with-the-management-toolbar}
The Management Toolbar lets you filter and sort your search container
results. While you can configure the toolbar's filters in the JSP, this
can quickly crowd the JSP. We recommend instead that you move this
functionality to a separate java class, which we refer to as a Display
Context throughout this tutorial.
There are two main types of filters: navigation and order. Both of these
are contained within the same dropdown menu. Follow the steps below to
create your filters.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Depending on your needs, there are two classes that you can extend for
your management toolbar Display Context. These base classes provide
the required methods to create your navigation and order filters:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-taglib/frontend-taglib-clay/src/main/java/com/liferay/frontend/taglib/clay/servlet/taglib/display/context/BaseManagementToolbarDisplayContext.java}{\texttt{BaseManagementToolbarDisplayContext}}:
for apps without a search container
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-taglib/frontend-taglib-clay/src/main/java/com/liferay/frontend/taglib/clay/servlet/taglib/display/context/SearchContainerManagementToolbarDisplayContext.java}{\texttt{SearchContainerManagementToolbarDisplayContext}}:
for apps with a search container (extends
\texttt{BaseManagementToolbarDisplayContext} and provides additional
logic for search containers)
\end{itemize}
An example configuration for each is shown below:
\texttt{BaseManagementToolbarDisplayContext} example:
\begin{verbatim}
public class MyManagementToolbarDisplayContext
extends BaseManagementToolbarDisplayContext {
public MyManagementToolbarDisplayContext(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse,
HttpServletRequest request) {
super(liferayPortletRequest, liferayPortletResponse, request);
}
...
}
\end{verbatim}
\texttt{SearchContainerManagementToolbarDisplayContext} example:
\begin{verbatim}
public class MyManagementToolbarDisplayContext
extends SearchContainerManagementToolbarDisplayContext {
public MyManagementToolbarDisplayContext(
LiferayPortletRequest liferayPortletRequest,
LiferayPortletResponse liferayPortletResponse,
HttpServletRequest request, SearchContainer searchContainer) {
super(
liferayPortletRequest, liferayPortletResponse, request,
searchContainer);
}
}
\end{verbatim}
\item
Override the \texttt{getNavigationKeys()} method to return the
navigation filter dropdown item(s). If your app doesn't require any
navigation filters, you can just provide the \emph{all} filter to
display everything. An example configuration is shown below:
\begin{verbatim}
@Override
protected String[] getNavigationKeys() {
return new String[] {"all", "pending", "done"};
}
\end{verbatim}
\item
override the \texttt{getOrderByKeys()} method to return the columns to
order. An example configuration is shown below:
\begin{verbatim}
@Override
protected String[] getOrderByKeys() {
return new String[] {"name", "items", "status"};
}
\end{verbatim}
\item
Open the JSP view that contains the Clay Management Toolbar and set
its \texttt{displayContext} attribute to the Display Context you
created. An example configuration is shown below:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Now you know how to configure the Management Toolbar's filters via a
Display Context.
\section{Related Topics}\label{related-topics-114}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar\#filtering-and-sorting-search-results}{Configuring
Filtering and Sorting Management Toolbar Attributes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-the-view-types}{Implementing
the View Types}
\end{itemize}
\chapter{Configuring Your Application's Title and Back
Link}\label{configuring-your-applications-title-and-back-link}
For administration applications, the title should be moved to the inner
views of the app and the associated back link should be moved to the
portlet titles. If you open the Blogs Admin application in the Control
Panel and add a new blog entry, you'll see this behavior in action:
\begin{figure}
\centering
\includegraphics{./images/new-blog-entry-title.png}
\caption{Adding a new blog entry displays the portlet title at the top,
along with a back link.}
\end{figure}
The Blogs Admin application is used as an example throughout this
article. Follow these steps to configure your app's title and back URL:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use \texttt{ParamUtil} to retrieve the redirect for the URL:
\begin{verbatim}
String redirect = ParamUtil.getString(request, "redirect");
\end{verbatim}
\item
Display the back icon and set the back URL to the \texttt{redirect}:
\begin{verbatim}
portletDisplay.setShowBackIcon(true);
portletDisplay.setURLBack(redirect);
\end{verbatim}
\item
Finally, set the title using the \texttt{renderResponse.setTitle()}
method. The example below provides a title for two scenarios:
\begin{itemize}
\tightlist
\item
If an existing blog entry is being updated, the blog's title is
displayed.
\item
Otherwise it defaults to \emph{New Blog Entry} since a new blog
entry is being created.
\end{itemize}
\begin{verbatim}
renderResponse.setTitle((entry != null) ? entry.getTitle() :
LanguageUtil.get(request, "new-blog-entry"));
%>
\end{verbatim}
\item
Update any back links in the view to use the \texttt{redirect}. The
Blog Admin app's \texttt{edit\_entry.jsp} form's cancel button is
shown below as an example:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Great! Now you know how to configure your app's title and back URL.
\section{Related Topics}\label{related-topics-115}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/applying-clay-patterns-to-navigation}{Applying
Clay Patterns to Your Navigation Bar}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/setting-empty-results-messages}{Setting
Empty Results Messages}
\end{itemize}
\chapter{Applying the Add Button
Pattern}\label{applying-the-add-button-pattern}
Clay's add button pattern is for actions that add entities (for example
a new blog entry button). It gives you a clean, minimal UI. You can use
it in any of your app's screens. Follow these steps to add a plus button
to your app:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{liferay-frontend} taglib declaration to your portlet's
\texttt{init.jsp}:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/frontend" prefix="liferay-frontend" %>
\end{verbatim}
\item
Add an
\href{https://docs.liferay.com/dxp/apps/frontend-taglib/latest/taglibdocs/liferay-frontend/add-menu.html}{\texttt{add-menu}
tag} to your portlet's view:
\begin{verbatim}
\end{verbatim}
\item
Nest a
\href{https://docs.liferay.com/dxp/apps/frontend-taglib/latest/taglibdocs/liferay-frontend/add-menu-item.html}{\texttt{\textless{}liferay-frontend:add-menu-item\textgreater{}}}
tag for every menu item you have. Here's an example of the add button
pattern with a single item:
\begin{verbatim}
\end{verbatim}
If there's only one item, the plus icon acts as a button that triggers
the item. If there's multiple items, clicking the plus icon displays a
menu containing them.
\begin{figure}
\centering
\includegraphics{./images/add-button-diagram.png}
\caption{The add button pattern consists of an \texttt{add-menu} tag
and at least one \texttt{add-menu-item} tag.}
\end{figure}
\end{enumerate}
The \texttt{com.liferay.mobile.device.rules.web} module's add menu is
shown below:
\begin{verbatim}
\end{verbatim}
There you have it! Now you know how to use the add button pattern.
\section{Related Topics}\label{related-topics-116}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/setting-empty-results-messages}{Setting
Empty Results Messages}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-the-management-toolbar}{Implementing
the Management Toolbar}
\end{itemize}
\chapter{Configuring Your Admin App's Actions
Menu}\label{configuring-your-admin-apps-actions-menu}
Rather then have a series of buttons or menus with actions in the
different views of the app, you can move all of these actions to the
upper right menu of the administrative portlet, leaving the primary
action (often an
\href{/docs/7-2/frameworks/-/knowledge_base/f/applying-the-add-button-pattern}{``Add''
operation}) visible in the add menu. For example, the web content
application has the actions menu shown below:
\begin{figure}
\centering
\includegraphics{./images/actions-menu.png}
\caption{The upper right ellipsis menu contains most of the actions for
the app.}
\end{figure}
Follow these steps to configure the actions menu in your admin app:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a \texttt{*ConfigurationIcon} Component class for the action
that extends the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/configuration/icon/BasePortletConfigurationIcon.html}{\texttt{BasePortletConfigurationIcon}
class} and implements the \texttt{PortletConfigurationIcon} service:
\begin{verbatim}
@Component(
immediate = true,
service = PortletConfigurationIcon.class
)
public class MyConfigurationIcon extends BasePortletConfigurationIcon {
...
}
\end{verbatim}
\item
Override the \texttt{getMessage()} method to specify the action's
label:
\begin{verbatim}
@Override
public String getMessage(PortletRequest portletRequest) {
ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
WebKeys.THEME_DISPLAY);
ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
themeDisplay.getLocale(), ExportAllConfigurationIcon.class);
return LanguageUtil.get(resourceBundle, "export-all-settings");
}
\end{verbatim}
\item
Override the \texttt{get()} method to specify whether the action is
invoked with the \texttt{GET} or \texttt{POST} method:
\begin{verbatim}
@Override
public String getMethod() {
return "GET";
}
\end{verbatim}
\item
Override the \texttt{getURL()} method to specify the URL (or
\texttt{onClick} JavaScript method) to invoke when the action is
clicked:
\begin{verbatim}
@Override
public String getURL(
PortletRequest portletRequest, PortletResponse portletResponse) {
LiferayPortletURL liferayPortletURL =
(LiferayPortletURL)_portal.getControlPanelPortletURL(
portletRequest, ConfigurationAdminPortletKeys.SYSTEM_SETTINGS,
PortletRequest.RESOURCE_PHASE);
liferayPortletURL.setResourceID("export");
return liferayPortletURL.toString();
}
\end{verbatim}
\item
Override the \texttt{getWeight()} method to specify the order that the
action should appear in the list:
\begin{verbatim}
@Override
public double getWeight() {
return 1;
}
\end{verbatim}
\item
Override the \texttt{isShow()} method to specify the context in which
the action should be displayed:
\begin{verbatim}
@Override
public boolean isShow(PortletRequest portletRequest) {
return true;
}
\end{verbatim}
\item
Define the view where you want to display the configuration options.
By default, if the portlet uses \texttt{mvcPath}, the global actions
(such as configuration, export/import, maximized, etc.) are displayed
for the JSP indicated in the initialization parameter of the portlet
\texttt{javax.portlet.init-param.view-template=/view.jsp}. The value
indicates the JSP where the global actions should be displayed.
However, if the portlet uses MVC Command, the views for the global
actions must be indicated with the initialization parameter
\texttt{javax.portlet.init-param.mvc-command-names-default-views=/wiki\_admin/view}
and the value must contain the \texttt{mvcRenderCommandName} where the
global actions should be displayed.
\item
If the portlet can be added to a page and you want to always include
the configuration options, add this initialization parameter to the
portlet's properties:
\begin{verbatim}
javax.portlet.init-param.always-display-default-configuration-icons=true
\end{verbatim}
In this example, the action appears in the System Settings portlet. To
make it appear in a secondary screen, you can use the \texttt{path}
property as shown below. The value of the \texttt{path} property
depends on the MVC framework used to develop the app. For the
MVCPortlet framework, provide the path (often a JSP) from the
\texttt{mvcPath} parameter. For MVCPortlet with MVC Commands, the path
should contain the \texttt{mvcRenderCommandName} where the actions
should be displayed (such as \texttt{/document\_library/edit\_folder}
for example):
\begin{verbatim}
@Component(
immediate = true,
property = {
"javax.portlet.name=" + ConfigurationAdminPortletKeys.SYSTEM_SETTINGS,
"path=/view_factory_instances"
},
service = PortletConfigurationIcon.class
)
public class ExportFactoryInstancesIcon extends BasePortletConfigurationIcon {
@Override
public String getMessage(PortletRequest portletRequest) {
ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
WebKeys.THEME_DISPLAY);
ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
themeDisplay.getLocale(), ExportFactoryInstancesIcon.class);
return LanguageUtil.get(resourceBundle, "export-entries");
}
@Override
public String getMethod() {
return "GET";
}
@Override
public String getURL(
PortletRequest portletRequest, PortletResponse portletResponse) {
LiferayPortletURL liferayPortletURL =
(LiferayPortletURL)_portal.getControlPanelPortletURL(
portletRequest, ConfigurationAdminPortletKeys.SYSTEM_SETTINGS,
PortletRequest.RESOURCE_PHASE);
ConfigurationModel factoryConfigurationModel =
(ConfigurationModel)portletRequest.getAttribute(
ConfigurationAdminWebKeys.FACTORY_CONFIGURATION_MODEL);
liferayPortletURL.setParameter(
"factoryPid", factoryConfigurationModel.getFactoryPid());
liferayPortletURL.setResourceID("export");
return liferayPortletURL.toString();
}
@Override
public double getWeight() {
return 1;
}
@Override
public boolean isShow(PortletRequest portletRequest) {
ConfigurationModelIterator configurationModelIterator =
(ConfigurationModelIterator)portletRequest.getAttribute(
ConfigurationAdminWebKeys.CONFIGURATION_MODEL_ITERATOR);
if (configurationModelIterator.getTotal() > 0) {
return true;
}
return false;
}
@Reference
private Portal _portal;
}
\end{verbatim}
\end{enumerate}
This covers some of the available methods. See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/configuration/icon/BasePortletConfigurationIcon.html}{Javadoc}
for a complete list of the available methods.
Great! Now you know how to configure your admin app's actions.
\section{Related Topics}\label{related-topics-117}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/applying-clay-patterns-to-navigation}{Applying
Clay Patterns to Your Navigation Bar}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-your-applications-title-and-back-link}{Configuring
Your Application's Title and Back Link}
\end{itemize}
\chapter{Setting Empty Results
Messages}\label{setting-empty-results-messages}
If you've toured the UI, you've probably noticed messages or possibly
even animations in the search containers when no results are found.
\begin{figure}
\centering
\includegraphics{./images/no-web-content-found.png}
\caption{This is a still frame from the Web Content portlet's empty
results animation.}
\end{figure}
You can configure your apps to use empty results messages and animations
too, thanks to the \texttt{liferay-frontend:empty-results-message} tag.
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{liferay-frontend} taglib declaration into your
portlet's \texttt{init.jsp}:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/frontend" prefix="liferay-frontend" %>
\end{verbatim}
\item
Add an
\href{https://docs.liferay.com/dxp/apps/frontend-taglib/latest/taglibdocs/liferay-frontend/empty-result-message.html}{\texttt{empty-result-message}
tag} to your portlet's view:
\begin{verbatim}
\end{verbatim}
\item
Configure the tag's attributes to define your search container's empty
results message, with or without an animation or image. The attributes
are described in the table below:
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
Attribute | Description |
--- | --- |
`actionDropdownItems` | Specifies the action or actions to display for the empty results in either a dropdown menu, a link, or a button, depending on the number of available actions. |
`animationType` | The CSS class for the animation. Four values are available by default with these CSS classes: `EmptyResultMessageKeys.AnimationType.EMPTY` (`taglib-empty-state`), `EmptyResultMessageKeys.AnimationType.SEARCH` (`taglib-search-state`), `EmptyResultMessageKeys.AnimationType.SUCCESS` (`taglib-success-state`), and `EmptyResultMessageKeys.AnimationType.NONE`. You can also specify a custom CSS class if you prefer. |
`componentId` | Specifies the ID for the `actionDropdownItems` component (dropdown menu, link, or button)|
`description` | The descriptive text to display beneath the main message. |
`elementType` | The type of element to replace the `x` parameter in the main message's language key `no-x-yet`. |
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
An example configuration is shown below:
```markup
```
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can replace the available default animations with your own
by overriding the `taglib-empty-state`, `taglib-search-state`, and
`taglib-success-state` CSS classes provided by
[_empty_result_message.scss](https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-css/frontend-css-web/src/main/resources/META-INF/resources/taglib/_empty_result_message.scss),
or by replacing the existing animations in the `@theme_image_path@/states/`
folder. Alternatively, you can also provide a new CSS class that defines
the animation and use it for the `animationType` attribute's value.
\end{verbatim}
\noindent\hrulefill
empty\_state.gif:
\begin{figure}
\centering
\includegraphics{./images/empty_state.gif}
\caption{If you can use the add button to add entities to the app, use
the empty state animation.}
\end{figure}
search\_state.gif:
\begin{figure}
\centering
\includegraphics{./images/search_state.gif}
\caption{If you can use the add button to add entities to the app, use
the search state animation.}
\end{figure}
success\_state.gif:
\begin{figure}
\centering
\includegraphics{./images/success_state.gif}
\caption{If you can use the add button to add entities to the app, use
the success state animation.}
\end{figure}
\noindent\hrulefill
\textbf{Note:} Empty results messages can also contain static images if
you prefer. Just use a valid image type instead. All animations must be
of type \texttt{GIF} though.
\noindent\hrulefill
Great! Now you know how to configure your app to display an empty
results message.
\section{Related Topics}\label{related-topics-118}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-front-end-taglibs-in-your-portlet}{Using
the Liferay Front-End Taglib}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/applying-the-add-button-pattern}{Applying
the Add Button Pattern}
\end{itemize}
\chapter{Search}\label{search}
Liferay DXP contains a search engine based on
\href{https://www.elastic.co/products/elasticsearch}{Elasticsearch}. You
can extend it, implement search for your own applications, and it's
highly configurable.
\section{Basic Search Concepts}\label{basic-search-concepts}
\textbf{Indexing}: During indexing, a document is sent to the search
engine. This document contains fields of various types (string, etc.).
The search engine processes each field and determines whether to store
the field or analyze it. Index time analysis can be configured for each
field (see Mapping Definitions).
For fields requiring analysis, the search engine first tokenizes the
value to obtain individual words or tokens. Then it passes each token
through a series of analyzers, which perform different functions. Some
remove common words or stop words (e.g., ``the'', ``and'', ``or'') while
others perform operations like lowercasing all characters.
\textbf{Searching}: Searching involves sending a search query and
obtaining results (a.k.a. hits) from the search engine. The search query
might contain both queries and filters (more on this later). Queries and
filters specify a field to search within and the value to match against.
The search engine iterates through each field within the nested queries
and filters and may perform special analysis prior to executing the
query (search time analysis). Search time analysis can be configured for
each field (see Mapping Definitions).
\section{Mapping Definitions}\label{mapping-definitions}
\emph{Mappings} control how a search engine processes a given field. For
instance, if a field name ends in ``es\_ES'', it should be processed as
Spanish, removing any common Spanish words like ``si''.
In Elasticsearch and Solr, the two supported search engines for Liferay
DXP, mappings are defined in \texttt{liferay-type-mappings.json} and
\texttt{schema.xml}, respectively.
The Elasticsearch mapping JSON file is in the Liferay DXP
\href{https://github.com/liferay/liferay-portal}{source code}, in the
\texttt{portal-search-elasticsearch6} module:
\begin{verbatim}
portal-search-elasticsearch6-impl/src/main/resources/META-INF/mappings/liferay-type-mappings.json
\end{verbatim}
The Solr \texttt{schema.xml} is in the \texttt{portal-search-solr7}
module's source code:
\begin{verbatim}
portal-search-solr7-impl/src/main/resources/META-INF/resources/schema.xml
\end{verbatim}
Access the Solr 7 module's source code from the \texttt{liferay-portal}
repository
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-search-solr7/portal-search-solr7-impl/src/main/resources/META-INF/resources/schema.xml}{here}.
You can customize these mappings to fit your needs. For example, you
might want to use a special analyzer for a custom inventory number
field.
\section{Liferay Search
Infrastructure}\label{liferay-search-infrastructure}
Search engines already provide native APIs, but Liferay wraps the engine
with a search infrastructure that does several things:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Index documents with the fields Liferay needs
(\texttt{entryClassName}, \texttt{entryClassPK},
\texttt{assetTagNames}, \texttt{assetCategories}, \texttt{companyId},
\texttt{groupId}, staging status, etc.).
\item
Apply the right filters to search queries (e.g., for scoping results).
\item
Apply permission checking on the results.
\item
Summarize returned results.
\end{enumerate}
That's just a taste of Liferay's Search Infrastructure. Continue reading
to learn more.
\chapter{Aggregations}\label{aggregations}
Aggregations take the results of a query and group the data into logical
sets. Aggregations can be composed to provide complex data summaries.
7.0 has a new API that exposes
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations.html}{Elasticsearch's
native Aggregation functionality}.
Currently, these aggregation types are supported:
\begin{itemize}
\tightlist
\item
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-bucket.html}{Bucketing
aggregations} create buckets of documents based on some criterion.
They support sub-aggregations.
\begin{itemize}
\tightlist
\item
Supported bucket aggregations include children aggregations, date
histogram aggregations, date range aggregations, diversified sampler
aggregations, filter aggregations, filters aggregations, geo
distance aggregations, geo hash grid aggregations, global
aggregations, histogram aggregations, missing aggregations, nested
aggregations, range aggregations, reverse nested aggregations,
sample aggregations, significant terms aggregations, significant
text aggregations, and terms aggregations.
\end{itemize}
\item
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-metrics.html}{Metrics
aggregations} compute metrics over a set of documents.
\begin{itemize}
\tightlist
\item
Supported metrics aggregations include average aggregations,
cardinality aggregations, extended stats aggregations, geo bounds
aggregations, geo centroid aggregations, max aggregations, min
aggregations, percentile ranks aggregations, percentiles
aggregations, scripted metric aggregations, stats aggregations, sum
aggregations, top hits aggregations, value count aggregations, and
weighted average aggregations.
\end{itemize}
\item
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-pipeline.html}{Pipeline
aggregations} aggregate the output of other aggregations and their
associated metrics.
\begin{itemize}
\tightlist
\item
Supported pipeline aggregations include average bucket pipeline
aggregations, bucket metrics pipeline aggregations, bucket script
pipeline aggregations, bucket selector pipeline aggregations, bucket
sort pipeline aggregations, cumulative sum pipeline aggregations,
derivative pipeline aggregations, extended stats bucket pipeline
aggregations, max bucket pipeline aggregations, min bucket pipeline
aggregations, moving function pipeline aggregations, percentiles
bucket pipeline aggregations, pipeline aggregations, serial diff
pipeline aggregations, stats bucket pipeline aggregations, and sum
bucket pipeline aggregations.
\end{itemize}
\end{itemize}
All the supported aggregations are found in the
\texttt{portal-search-api} module's
\texttt{com.liferay.portal.search.aggregation} package.
In addition to these aggregations, other aggregation-like features are
present in the Liferay DXP search API:
\textbf{Group By} collects search results (documents) based on a
particular field. For example, if you want to group the search results
based on the asset type (e.g., web content article, document, blog post,
etc.), you can create a search query that contains a
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-kernel/src/com/liferay/portal/kernel/search/GroupBy.java}{com.liferay.portal.kernel.search.GroupBy}
aggregation with the field \texttt{entryClassName}.
You can specify these attributes for returned groups:
\begin{itemize}
\tightlist
\item
The maximum number of results in each group
\item
Special sorting for the grouped results
\end{itemize}
\textbf{Facets} act like bucket aggregations, holding results that share
a certain characteristic.
\section{Using Aggregations}\label{using-aggregations}
The generalized approach for using aggregations in your own search code
is like this:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Instantiate and construct the aggregation object
\item
Add the aggregation information to the search request
\item
Process the search response
\end{enumerate}
These steps are covered in more detail (with examples)
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-aggregations-in-low-level-search-calls}{here}.
\section{External References}\label{external-references}
\begin{itemize}
\tightlist
\item
\url{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations.html}
\item
\url{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations.html\#_structuring_aggregations}
\end{itemize}
\section{Search Engine Connector
Support}\label{search-engine-connector-support}
\begin{itemize}
\tightlist
\item
Elasticsearch 6: Yes
\item
Solr 7: No
\end{itemize}
\section{New/Related APIs}\label{newrelated-apis}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2558}}
>{\centering\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.5116}}
>{\centering\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2326}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
API (FQCN)
\end{minipage} & \begin{minipage}[b]{\linewidth}\centering
Provided by Artifact
\end{minipage} & \begin{minipage}[b]{\linewidth}\centering
Notes
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{com.liferay.portal.search.aggregation.*} &
com.liferay.portal.search.api & The whole
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/aggregation}{``aggregation''
package} is new as of 7.0 \\
\end{longtable}
\chapter{Creating Aggregations}\label{creating-aggregations}
Each aggregation has a different purpose and should be processed
differently once returned from the search engine, but you add the
aggregation information to the search request in a similar way between
all aggregations.
\section{Instantiate and Construct the
Aggregation}\label{instantiate-and-construct-the-aggregation}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use the \texttt{com.liferay.portal.search.aggregation.Aggregations} to
instantiate the aggregation you'll construct. For example,
\begin{verbatim}
PercentilesAggregation percentilesAggregation =
aggregations.percentiles("percentiles", Field.PRIORITY);
\end{verbatim}
To discover what fields each aggregation must have (e.g.,
\texttt{Sting\ name,\ String\ field} in the case of the above
\texttt{PercentilesAggregation}), see the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/aggregation/Aggregations.java}{\texttt{Aggregations}
interface}.
\item
Build out the aggregation to get the desired response. This looks
different for each aggregation type, but Elasticsearch's documentation
on the aggregation type explains it well (such as
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-metrics-percentile-aggregation.html}{Percentiles
Aggregations}) combined with the setters in Liferay's corresponding
interface.
For example, use the
\texttt{setPercentilesMethod(PercentilesMethod.HDR)} method to use the
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-metrics-percentile-aggregation.html\#_hdr_histogram}{High
Dynamic Range Histogram} for calculating the percentiles.
\begin{verbatim}
percentilesAggregation.setPercentilesMethod(PercentilesMethod.HDR);
\end{verbatim}
\end{enumerate}
Once the aggregation itself is in good shape, feed it to the search
query.
\section{Build the Search Query}\label{build-the-search-query}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get an instance of
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilder} from
the \texttt{SearchRequestBuilderFactory} service:
\begin{verbatim}
SearchRequestBuilder searchRequestBuilder =
searchRequestBuilderFactory.builder();
\end{verbatim}
\item
Get a \texttt{com.liferay.portal.search.searcher.SearchRequest}
instance from the builder, then add the aggregation to it and run its
\texttt{build} method:
\begin{verbatim}
SearchRequest searchRequest =
searchRequestBuilder.addAggregation(percentilesAggregation).build();
\end{verbatim}
\end{enumerate}
\section{Execute the Search Query}\label{execute-the-search-query}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Perform a search using the \texttt{Searcher} service and the
\texttt{SearchRequest} to get a
\texttt{com.liferay.portal.search.searcher.SearchResponse}:
\begin{verbatim}
SearchResponse searcher.search(searchRequest);
\end{verbatim}
\item
To satisfy the dependencies of the example code here, get a reference
to
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilderFactory}
and \texttt{com.liferay.portal.search.searcher.Searcher}:
\begin{verbatim}
@Reference
protected Searcher searcher;
@Reference
SearchRequestBuilderFactory searchRequestBuilderFactory;
\end{verbatim}
\end{enumerate}
\section{Process the response}\label{process-the-response}
What you'll do with the \texttt{SearchResponse} returned by the
\texttt{searcher.search} call depends on the type of aggregation and
your specific use case. A separate article will be written to
demonstrate how to process the response.
\chapter{Statistical Aggregations}\label{statistical-aggregations}
Support for
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/portal-kernel/src/com/liferay/portal/kernel/search/GroupBy.java}{GroupBy}
and
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/portal-kernel/src/com/liferay/portal/kernel/search/Stats.java}{Stats}
aggregations were introduced in 7.0.
Cardinality Aggregations extend Liferay DXP's metrics aggregation
capabilities, providing an approximate (i.e., statistical) count of
distinct values returned by a search query. For example, you could
compute a count of distinct values of the \emph{tag} field. Refer to the
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-metrics-cardinality-aggregation.html}{Elasticsearch
documentation} for more details.
While this functionality was available in the past directly in the
portal kernel code, it's been extracted and re-implemented in
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/stats/StatsRequest.java}{\texttt{StatsRequest}}
and
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/stats/StatsResponse.java}{\texttt{StatsResponse}},
both introduced in the \texttt{com.liferay.portal.search.api} module to
avoid modifying \texttt{portal-kernel}. \texttt{StatsRequest} provides
the same statistical features that the legacy
\texttt{com.liferay.portal.kernel.search.Stats} does, and adds the new
cardinality option.
\section{\texorpdfstring{\texttt{StatsRequest}}{StatsRequest}}\label{statsrequest}
The \texttt{StatsRequest} Provides a map of field names and the metric
aggregations that are to be computed for each field.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a reference to
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilderFactory}:
\begin{verbatim}
@Reference
SearchRequestBuilderFactory searchRequestBuilderFactory;
\end{verbatim}
\item
Get an instance of
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilder}:
\begin{verbatim}
SearchRequestBuilder searchRequestBuilder = searchRequestBuilderFactory.builder();
\end{verbatim}
\item
Get a \texttt{com.liferay.portal.search.searcher.SearchRequest}
instance from the builder:
\begin{verbatim}
SearchRequest searchRequest = searchRequestBuilder.build();
\end{verbatim}
\item
Get a reference to
\texttt{com.liferay.portal.search.stats.StatsRequestBuilderFactory}:
\begin{verbatim}
@Reference
StatsRequestBuilderFactory statsRequestBuilderFactory;
\end{verbatim}
\item
Get a \texttt{com.liferay.portal.search.stats.StatsRequestBuilder}
instance and build
\texttt{com.liferay.portal.search.stats.StatsRequest} with the desired
metrics:
\begin{verbatim}
StatsRequestBuilder statsRequestBuilder =
statsRequestBuilderFactory.getStatsRequestBuilder();
StatsRequest statsRequest = statsRequestBuilder
.cardinality(true)
.count(true)
.field(field)
.max(true)
.mean(true)
.min(true)
.missing(true)
.sum(true)
.sumOfSquares(true)
.build();
\end{verbatim}
\item
Set \texttt{StatsRequest} on the \texttt{SearchRequest}:
\begin{verbatim}
searchRequest.statsRequests(statsRequest);
\end{verbatim}
\item
Get a reference to
\texttt{com.liferay.portal.search.searcher.Searcher}:
\begin{verbatim}
@Reference
protected Searcher searcher;
\end{verbatim}
\item
Perform a search using \texttt{Searcher} and \texttt{SearchRequest} to
get \texttt{com.liferay.portal.search.searcher.SearchResponse}:
\begin{verbatim}
SearchResponse searcher.search(searchRequest);
\end{verbatim}
\end{enumerate}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-test-util/src/main/java/com/liferay/portal/search/test/util/stats/BaseStatisticsTestCase.java\#L128}{\textbf{Click
here to see an example from Liferay's codebase}}
\section{\texorpdfstring{\texttt{StatsResponse}}{StatsResponse}}\label{statsresponse}
The stats response contains the metrics aggregations computed by the
search engine for a given field.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get the map containing the metrics aggregations computed by the search
engine:
\begin{verbatim}
Map map = searchResponse.getStatsResponseMap();
\end{verbatim}
\item
Get the \texttt{StatsResponse} for a given field:
\begin{verbatim}
StatsResponse statsResponse = map.get(field);
\end{verbatim}
\item
Get the desired metric, for example \emph{cardinality}:
\begin{verbatim}
statsResponse.getCardinality();
\end{verbatim}
\end{enumerate}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-test-util/src/main/java/com/liferay/portal/search/test/util/stats/BaseStatisticsTestCase.java\#L128}{\textbf{Click
here to see an example from Liferay's codebase}}
\section{\texorpdfstring{Using the Legacy \texttt{Stats}
Object}{Using the Legacy Stats Object}}\label{using-the-legacy-stats-object}
The legacy \texttt{com.liferay.portal.kernel.search.Stats} object
continues to be fully supported:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a \texttt{Stats} instance with the desired metrics:
\begin{verbatim}
Stats stats = new Stats() {
{
setCount(true);
setField(field);
setMax(true);
setMean(true);
setMin(true);
setSum(true);
setSumOfSquares(true);
}
};
\end{verbatim}
\item
Set \texttt{Stats} on the \texttt{SearchContext}:
\begin{verbatim}
searchRequestBuilder.withSearchContext(searchContext -> searchContext.addStats(stats));
\end{verbatim}
\end{enumerate}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-test-util/src/main/java/com/liferay/portal/search/test/util/stats/BaseStatisticsTestCase.java\#L42}{\textbf{Click
here to see an example from Liferay's codebase}}
\section{External References}\label{external-references-1}
\begin{itemize}
\tightlist
\item
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-metrics.html
\item
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-metrics-cardinality-aggregation.html
\item
https://lucene.apache.org/solr/guide/7\_5/the-stats-component.html
\end{itemize}
\section{Search Engine Connector
Support}\label{search-engine-connector-support-1}
\begin{itemize}
\tightlist
\item
Elasticsearch 6: Yes
\item
Solr 7: Yes
\end{itemize}
\section{New/Related APIs}\label{newrelated-apis-1}
These are the relevant APIs for building Statistics Aggregations:
API (FQCN) \textbar{} Provided by Artifact \textbar{}
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilder\#statsRequests(StatsRequest...\ statsRequests)}
\textbar{} \texttt{com.liferay.portal.search.api}
\texttt{com.liferay.portal.search.searcher.SearchResponse\#getStatsResponseMap()}
\textbar{} \texttt{com.liferay.portal.search.api}
\textbf{\texttt{com.liferay.portal.search.stats.StatsRequest}}
\textbar{} \texttt{com.liferay.portal.search.api}
\texttt{com.liferay.portal.search.stats.StatsRequestBuilder} \textbar{}
\texttt{com.liferay.portal.search.api}
\texttt{com.liferay.portal.search.stats.StatsRequestBuilderFactory}
\textbar{} \texttt{com.liferay.portal.search.api}
\textbf{\texttt{com.liferay.portal.search.stats.StatsResponse}}
\textbar{} \texttt{com.liferay.portal.search.api}
\texttt{com.liferay.portal.kernel.search.Stats} \textbar{}
\texttt{portal-kernel}
\section{Deprecated APIs}\label{deprecated-apis}
\begin{itemize}
\tightlist
\item
SearchSearchRequest\#getStats()
\item
SearchSearchRequest\#setStats(Map\textless String, Stats\textgreater{}
stats)
\end{itemize}
\chapter{Model Entity Indexing
Framework}\label{model-entity-indexing-framework}
Unless you're searching for model entities using database queries (not
recommended in most cases), each asset in Liferay DXP must be indexed in
the search engine. The indexing code is specific to each asset, as the
asset's developers know what fields to index and what filters to apply
to the search query. This paradigm applies to Liferay's own developers
and anyone developing model entities for use with Liferay DXP.
In past versions of Liferay DXP, when your asset required indexing, you
would implement a new Indexer by extending
\texttt{com.liferay.portal.kernel.search.BaseIndexer\textless{}T\textgreater{}}.
Liferay DXP version 7.1 introduced a new pattern that relies on
\href{https://stackoverflow.com/questions/2399544/difference-between-inheritance-and-composition}{composition
instead of inheritance}. That said, if you want to use the old approach,
feel free to extend \texttt{BaseIndexer}. It's still supported.
\section{Search and Indexing
Overview}\label{search-and-indexing-overview}
Starting with the 7.0 version of Liferay DXP, the Search API has become
less tied to Lucene. Elasticsearch support was added (in addition to
Solr), and most of the newer searching and indexing APIs aim to
leverage/map Elasticsearch APIs. This means that in many cases (for
example the \texttt{Query} types) there is a one-to-one mapping between
the Liferay and Elasticsearch APIs.
In addition to the Elasticsearch-centered APIs, Liferay's Search
Infrastructure includes additional APIs serving these purposes:
\begin{itemize}
\tightlist
\item
Ensure all indexed documents include some required fields (e.g.,
\texttt{entryClassName}, \texttt{entryClassPK},
\texttt{assetTagNames}, \texttt{assetCategories}, \texttt{companyId},
\texttt{groupId}, staging status).
\item
Ensure the scope of returned search results is appropriate by applying
the right filters to search requests.
\item
Provide permission checking and hit summaries for display in the
built-in \href{/docs/7-2/user/-/knowledge_base/u/search}{search
application}.
\end{itemize}
\section{\texorpdfstring{Mapping the Composite Search and Indexing
Framework to \texttt{Indexer}/\texttt{BaseIndexer}
Code}{Mapping the Composite Search and Indexing Framework to Indexer/BaseIndexer Code}}\label{mapping-the-composite-search-and-indexing-framework-to-indexerbaseindexer-code}
If you're used to the old way of indexing custom entities (extending
\texttt{BaseIndexer}, the abstract implementation of \texttt{Indexer}),
the table below provides a quick overview about how the methods of the
\texttt{Indexer} interface were decomposed into several new classes and
methods.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3857}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3857}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2286}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Indexer/BaseIndexer method
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Composite Indexer Equivalent
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Example
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Class Constructor & \texttt{SearchRegistrar} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/BlogsEntrySearchRegistrar.java}{\texttt{BlogsEntrySearchRegistrar}} \\
\texttt{setDefaultSelectedFieldNames} &
\texttt{SearchRegistrar.activate} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/BlogsEntrySearchRegistrar.java}{\texttt{BlogsEntrySearchRegistrar}} \\
\texttt{setDefaultSelectedLocalizedFieldNames} &
\texttt{SearchRegistrar.activate} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/BlogsEntrySearchRegistrar.java}{\texttt{BlogsEntrySearchRegistrar}} \\
\texttt{setPermissionAware} & \texttt{ModelResourcePermissionRegistrar}
&
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/document-library/document-library-service/src/main/java/com/liferay/document/library/internal/security/permission/resource/DLFileEntryModelResourcePermissionRegistrar.java}{\texttt{DLFileEntryModelResourcePermissionRegistrar}} \\
\texttt{setFilterSearch} & \texttt{ModelResourcePermissionRegistrar} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/document-library/document-library-service/src/main/java/com/liferay/document/library/internal/security/permission/resource/DLFileEntryModelResourcePermissionRegistrar.java}{\texttt{DLFileEntryModelResourcePermissionRegistrar}} \\
\texttt{getDocument}/\texttt{doGetDocument} &
\texttt{ModelDocumentContributor} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/spi/model/index/contributor/BlogsEntryModelDocumentContributor.java}{\texttt{BlogsEntryModelDocumentContributor}} \\
\texttt{reindex}/\texttt{doReindex} &
\texttt{ModelIndexerWriterContributor} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/spi/model/index/contributor/BlogsEntryModelIndexerWriterContributor.java}{\texttt{BlogsEntryModelIndexerWriterContributor}} \\
\texttt{addRelatedEntryFields} & \texttt{RelatedEntryIndexer} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/document-library/document-library-service/src/main/java/com/liferay/document/library/internal/search/DLFileEntryRelatedEntryIndexer.java}{\texttt{DLFileEntryRelatedEntryIndexer}} \\
\texttt{postProcessContextBooleanFilter}/\texttt{PostProcessContextQuery}
& \texttt{ModelPreFilterContributor} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/spi/model/query/contributor/BlogsEntryModelPreFilterContributor.java}{\texttt{BlogsEntryModelPreFilterContributor}} \\
\texttt{postProcessSearchQuery} & \texttt{KeywordQueryContributor} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/spi/model/query/contributor/BlogsEntryKeywordQueryContributor.java}{\texttt{BlogsEntryKeywordQueryContributor}} \\
\texttt{getFullQuery} & \texttt{SearchContextContributor} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/document-library/document-library-service/src/main/java/com/liferay/document/library/internal/search/DLFileEntryModelSearchContextContributor.java}{\texttt{DLFileEntryModelSearchContextContributor}} \\
\texttt{isVisible}/\texttt{isVisibleRelatedEntry} &
\texttt{ModelVisibilityContributor} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/spi/model/result/contributor/BlogsEntryModelVisibilityContributor.java}{\texttt{BlogsEntryModelVisibilityContributor}} \\
\texttt{getSummary}/\texttt{createSummary}/\texttt{doGetSummary} &
\texttt{ModelSummaryContributor} &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/internal/search/spi/model/result/contributor/BlogsEntryModelSummaryContributor.java}{\texttt{BlogsEntryModelSummaryContributor}} \\
\texttt{Indexer.search}/\texttt{searchCount} & No change &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/blogs/blogs-web/src/main/java/com/liferay/blogs/web/internal/display/context/BlogEntriesDisplayContext.java}{\texttt{BlogEntriesDisplayContext}} \\
\texttt{Indexer.delete}/\texttt{doDelete} & No change &
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/message-boards/message-boards-service/src/main/java/com/liferay/message/boards/service/impl/MBMessageLocalServiceImpl.java\#L703}{\texttt{MBMessageLocalServiceImpl.deleteMessage}} \\
\end{longtable}
\noindent\hrulefill
In addition, you can index \texttt{ExpandoBridge} attributes. This was
previously accomplished in \texttt{BaseIndexer}'s
\texttt{getBaseModelDocument}. Now you implement an
\texttt{ExpandoBridgeRetriever}. See
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/document-library/document-library-service/src/main/java/com/liferay/document/library/internal/search/DLFileEntryExpandoBridgeRetriever.java}{\texttt{DLFileEntryExpandoBridgeRetriever}}
for an example implementation.
\section{Permissions-Aware Searching and
Indexing}\label{permissions-aware-searching-and-indexing}
In previous versions of Liferay DXP, search was only \emph{permissions
aware} (indexed with the entity's permissions and searched with those
permissions intact) if the application developer specified this line in
the \texttt{Indexer} class's constructor:
\begin{verbatim}
setPermissionAware(true);
\end{verbatim}
Now, search is permissions aware by default \emph{if the new permissions
approach}, as described in
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{these
tutorials}, is implemented for an application.
\section{Annotating Service Methods to Trigger
Indexing}\label{annotating-service-methods-to-trigger-indexing}
Having objects translated into database entities \emph{and} search
engine documents means that there's a possibility for a state mismatch
between the database and search engine. For example, when a Blogs Entry
is added, updated, or removed from the database, corresponding changes
must be made in the search engine. To do this, intervention must be made
in the service layer. For Service Builder entities, this occurs in the
\texttt{LocalServiceImpl} classes. An annotation simplifies this:
\texttt{@Indexable}. It takes a \texttt{type} property that can have two
values: \texttt{REINDEX} or \texttt{DELETE}. Commonly, a
\texttt{deleteEntity} method in the service layer is annotated like
this:
\begin{verbatim}
@Indexable(type = IndexableType.DELETE)
@Override
@SystemEvent(type = SystemEventConstants.TYPE_DELETE)
public BlogsEntry deleteEntry(BlogsEntry entry) throws PortalException {
...
}
\end{verbatim}
The \texttt{@Indexable} annotation is executed by Liferay's AOP
infrastructure, so if you have a method with that annotation, you must
call it using a service instance variable injected by your dependency
injector, and not using the \texttt{this} keyword. Whether using OSGi's
Declarative Services (DS) or Spring for dependency injection, there's a
protected variable declared in the superclass
(\texttt{*LocalServiceBaseImpl}) that can be used in the
\texttt{*LocalServiceImpl}, like this.
\begin{verbatim}
blogsEntryLocalService.deleteEntry(entry);
\end{verbatim}
Since you're using the injected service variable, that means you must
not call
\begin{verbatim}
this.deleteEntry(...)
\end{verbatim}
in your \texttt{*LocalServiceImpl} methods. The annotation won't be
executed and you'll be left with a state mismatch between the search
engine document and the database column.
\section{Search and Localization: a Cheat
Sheet}\label{search-and-localization-a-cheat-sheet}
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization}
is important. Search and localization can play nicely together, if you
take some precautions:
\begin{itemize}
\tightlist
\item
For each field that should be localized (e.g., \texttt{content}),
index a separate field for each of the site's languages (e.g.,
\texttt{content\_en\_US}, \texttt{content\_ja\_JP},
\texttt{content\_es\_ES}, \ldots).
\item
Search the localized fields. Whatever you index, that's what you
should be querying for.
\item
Don't index content in plain (unlocalized) fields if you expect the
content to be present in multiple locales.
\item
Don't index both the plain and the localized field.
\end{itemize}
The indexing and searching articles included in this section demonstrate
how to handle localized fields in the search code properly.
\chapter{Indexing Model Entities}\label{indexing-model-entities}
Model entities are searchable when their data fields are indexed by the
search engine. Search and indexing code relies on Search APIs and SPIs.
The extension points (i.e., the interfaces to implement) in this section
are provided by the \texttt{com.liferay.portal.search.spi} bundle. Calls
are also made to the \texttt{com.liferay.portal.search.api} bundle's
methods.
Here are the Gradle dependencies for Liferay DXP 7.2.0 GA1:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay", name: "com.liferay.portal.search.spi", version: "3.2.1"
compileOnly group: "com.liferay", name: "com.liferay.portal.search.api", version: "3.7.0"
}
\end{verbatim}
\noindent\hrulefill
\textbf{APIs and SPIs:} SPIs are a special type of API. Generally, code
inside a SPI module (e.g., \texttt{portal-search-spi}) is used to
customize existing behavior, while API modules contain behavior you want
to use. Put simply, implement interfaces from an SPI, and consume the
code from the API.
SPI example: \texttt{ModelDocumentContributor} lives in an SPI module
because you're supposed to implement it directly, defining your own
indexing behavior.
API example: \texttt{SearchRequest} lives in an API module because its
behavior is leveraged inside your code to build a search request.
\noindent\hrulefill
\section{Contributing Model Entity Fields to the
Index}\label{contributing-model-entity-fields-to-the-index}
Write a \texttt{ModelDocumentContributor} class to control how model
entity fields are indexed in search engine documents.
\textbf{Extension Point (SPI):}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/index/contributor/ModelDocumentContributor.java}{\texttt{com.liferay.portal.search.spi.model.index.contributor.ModelDocumentContributor\textless{}T\textgreater{}}}
Declare the Component's \texttt{indexer.class.name} and its service type
as a \texttt{ModelDocumentContributor} class:
\begin{verbatim}
@Component(
immediate = true,
property = "indexer.class.name=com.liferay.foo.model.FooEntry",
service = ModelDocumentContributor.class
)
public class FooEntryModelDocumentContributor
implements ModelDocumentContributor {
\end{verbatim}
Implement the \texttt{contribute} method, calling the
\texttt{com.liferay.portal.kernel.Document.add()} method appropriate for
the data type the index should use (e.g., \texttt{addText} for fields
that should be searched using a
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/text.html}{full
text search strategy}).
\begin{verbatim}
@Override
public void contribute(Document document, FooEntry fooEntry) {
document.addDate(Field.DISPLAY_DATE, fooEntry.getDisplayDate());
document.addDate(Field.MODIFIED_DATE, fooEntry.getModifiedDate());
document.addText(Field.SUBTITLE, fooEntry.getSubtitle());
\end{verbatim}
For fields that should be
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{localized},
index a field for each locale in the Site. Many times you'll want to
localize the title and content fields, for example:
\begin{verbatim}
for (Locale locale :
LanguageUtil.getAvailableLocales(fooEntry.getGroupId())) {
String languageId = LocaleUtil.toLanguageId(locale);
document.addText(
LocalizationUtil.getLocalizedName(Field.CONTENT, languageId),
content);
document.addText(
LocalizationUtil.getLocalizedName(Field.TITLE, languageId),
fooEntry.getTitle());
}
\end{verbatim}
The above strategy is a good one: loop through the available locales in
the site, then use
\texttt{com.liferay.portal.kernel.util.LocalizationUtil} to add the
localized field name to the document.
The \texttt{contribute} method is called each time the \texttt{add} and
\texttt{update} methods in the entity's service layer are called.
\section{Configure Re-Indexing and Batch Indexing
Behavior}\label{configure-re-indexing-and-batch-indexing-behavior}
\texttt{ModelIndexerWriterContributor} classes configure the re-indexing
and batch re-indexing behavior for the model entity. This class's
\texttt{customize} method is called when a re-index is triggered from
the Search administrative application found in Control Panel →
Configuration → Search, or when a CRUD operation is made on the entity,
\emph{if} the \texttt{modelIndexed} method is implemented in the
contributor.
\textbf{Extension Point (SPI):}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/index/contributor/ModelIndexerWriterContributor.java}{\texttt{com.liferay.portal.search.spi.model.index.contributor.ModelIndexerWriterContributor}}
The bulk of the work is in the \texttt{customize} method. This code uses
the actionable dynamic query helper method to retrieve all existing Foo
entities in the virtual instance (identified by the Company ID). If
you're using Service Builder, this query method was generated for you
when you built the services. Each Foo Entry document is then retrieved
and added to a collection.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
First set up the component and class declarations:
\begin{verbatim}
@Component(
immediate = true,
property = "indexer.class.name=com.liferay.foo.model.FooEntry",
service = ModelIndexerWriterContributor.class
)
public class FooEntryModelIndexerWriterContributor
implements ModelIndexerWriterContributor {
\end{verbatim}
\item
Write the \texttt{customize} method:
\begin{verbatim}
@Override
public void customize(
BatchIndexingActionable batchIndexingActionable,
ModelIndexerWriterDocumentHelper modelIndexerWriterDocumentHelper) {
batchIndexingActionable.setAddCriteriaMethod(
dynamicQuery -> {
Property displayDateProperty = PropertyFactoryUtil.forName(
"displayDate");
dynamicQuery.add(displayDateProperty.lt(new Date()));
Property statusProperty = PropertyFactoryUtil.forName("status");
Integer[] statuses = {
WorkflowConstants.STATUS_APPROVED,
WorkflowConstants.STATUS_IN_TRASH
};
dynamicQuery.add(statusProperty.in(statuses));
});
batchIndexingActionable.setPerformActionMethod(
(FooEntry fooEntry) -> {
Document document =
modelIndexerWriterDocumentHelper.getDocument(fooEntry);
batchIndexingActionable.addDocuments(document);
});
}
\end{verbatim}
\item
Override \texttt{getBatchIndexingActionable}:
\begin{verbatim}
@Override
public BatchIndexingActionable getBatchIndexingActionable() {
return _dynamicQueryBatchIndexingActionableFactory.
getBatchIndexingActionable(
_fooEntryLocalService.getIndexableActionableDynamicQuery());
}
\end{verbatim}
\item
Override \texttt{getcompanyId}, getting the ID from your entity:
\begin{verbatim}
@Override
public long getCompanyId(FooEntry fooEntry) {
return fooEntry.getCompanyId();
}
\end{verbatim}
\item
Override \texttt{getIndexerWriterMode}:
\begin{verbatim}
@Override
public IndexerWriterMode getIndexerWriterMode(FooEntry fooEntry) {
int status = fooEntry.getStatus();
if ((status == WorkflowConstants.STATUS_APPROVED) ||
(status == WorkflowConstants.STATUS_IN_TRASH) ||
(status == WorkflowConstants.STATUS_DRAFT)) {
return IndexerWriterMode.UPDATE;
}
return IndexerWriterMode.DELETE;
}
\end{verbatim}
\texttt{com.liferay.portal.search.spi.model.index.contributor.helper.IndexerWriterMode}
defines the following index-writing options:
\begin{itemize}
\tightlist
\item
\texttt{IndexerWriterMode.DELETE}: instructs the search framework to
delete the given document in the search index without re-creating it
\item
\texttt{IndexerWriterMode.PARTIAL\_UPDATE},
\texttt{IndexerWriterMode.UPDATE}: instructs the search framework to
update the given document in the search index.
\item
\texttt{IndexerWriterMode.SKIP}: instructs the search framework to
not write anything to the search index.
\end{itemize}
The default is \texttt{IndexerWriterMode.UPDATE}.
\item
Add the services referenced:
\begin{verbatim}
@Reference
private FooEntryLocalService _fooEntryLocalService;
@Reference
private DynamicQueryBatchIndexingActionableFactory
_dynamicQueryBatchIndexingActionableFactory;
\end{verbatim}
\end{enumerate}
\section{Contribute Fields to Every
Document}\label{contribute-fields-to-every-document}
\texttt{DocumentContributor} classes (without any
\texttt{indexer.class.name} component property or type parameter)
contribute certain fields to every index document, regardless of what
base entity is being indexed. For example, the
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search/src/main/java/com/liferay/portal/search/internal/contributor/document/GroupedModelDocumentContributor.java}{\texttt{GroupedModelDocumentContributor}}
contains logic to contribute \texttt{GROUP\_ID} and
\texttt{SCOPE\_GROUP\_ID} fields for all documents with a backing entity
that's also a \texttt{GroupedModel}.
\chapter{Searching the Index for Model
Entities}\label{searching-the-index-for-model-entities}
The heart of searching for your model entity documents is querying for
what you indexed. To do this, contribute search terms to the Liferay DXP
search query.
The extension points (i.e., the interfaces to implement) on this page
are provided by the \texttt{com.liferay.portal.search.spi} bundle.
Here's the Gradle dependency for Liferay DXP 7.2.0 GA1:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay", name: "com.liferay.portal.search.spi", version: "3.2.1"
}
\end{verbatim}
\section{Adding your Model Entity's Terms to the
Query}\label{adding-your-model-entitys-terms-to-the-query}
\texttt{KeywordQueryContributor} classes contribute clauses to the
ongoing search query, to control the way model entities are searched. If
you're storing localized fields in the index (a good idea, as covered in
the example code for your \texttt{ModelDocumentContributor}), query the
localized fields at search time.
\textbf{Extension Point (SPI):}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/query/contributor/KeywordQueryContributor.java}{com.liferay.portal.search.spi.model.query.contributor.KeywordQueryContributor}
\begin{verbatim}
@Component(
immediate = true,
property = "indexer.class.name=com.liferay.foo.model.FooEntry",
service = KeywordQueryContributor.class
)
public class FooEntryKeywordQueryContributor
implements KeywordQueryContributor {
@Override
public void contribute(
String keywords, BooleanQuery booleanQuery,
KeywordQueryContributorHelper keywordQueryContributorHelper) {
SearchContext searchContext =
keywordQueryContributorHelper.getSearchContext();
queryHelper.addSearchLocalizedTerm(
booleanQuery, searchContext, Field.CONTENT, false);
queryHelper.addSearchTerm(
booleanQuery, searchContext, Field.SUBTITLE, false);
queryHelper.addSearchLocalizedTerm(
booleanQuery, searchContext, Field.TITLE, false);
}
@Reference
protected QueryHelper queryHelper;
}
\end{verbatim}
The entity in this example has a title, subtitle, and content field. The
subtitle field wasn't stored as a localized field, so it's not searched
that way, either.
\section{Contributing Query Clauses to Every
Search}\label{contributing-query-clauses-to-every-search}
It's a less common need, but to contribute query clauses to every
search, regardless of what base entity is being searched, implement a
\texttt{KeywordQueryContributor} class registered without an
\texttt{indexer.class.name} component property. For example, see
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search/src/main/java/com/liferay/portal/search/internal/contributor/query/AlwaysPresentFieldsKeywordQueryContributor.java}{\texttt{AlwaysPresentFieldsKeywordQueryContributor}}.
It includes a String array that includes the fields that are always
searched:
\begin{verbatim}
private static final String[] _ALWAYS_PRESENT_FIELDS = {
Field.COMMENTS, Field.CONTENT, Field.DESCRIPTION, Field.PROPERTIES,
Field.TITLE, Field.URL, Field.USER_NAME
};
\end{verbatim}
\section{Pre-Filtering}\label{pre-filtering}
\texttt{*PreFilterContributor} classes control how search results are
filtered before they're returned from the search engine. For example,
adding the workflow status to the query ensures that an entity in the
trash isn't returned in the search results. They're constructed each
time a query for the model entity is made.
\textbf{Extension Pointi (SPI):}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/query/contributor/ModelPreFilterContributor.java}{\texttt{ModelPreFilterContributor}s}
To contribute filter clauses specific to a model entity, use a
\texttt{ModelPreFilterContributor}. This one adds a filter for workflow
status:
\begin{verbatim}
@Component(
immediate = true,
property = "indexer.class.name=com.liferay.foo.model.FooEntry",
service = ModelPreFilterContributor.class
)
public class FooEntryModelPreFilterContributor
implements ModelPreFilterContributor {
@Override
public void contribute(
BooleanFilter booleanFilter, ModelSearchSettings modelSearchSettings,
SearchContext searchContext) {
addWorkflowStatusFilter(
booleanFilter, modelSearchSettings, searchContext);
}
protected void addWorkflowStatusFilter(
BooleanFilter booleanFilter, ModelSearchSettings modelSearchSettings,
SearchContext searchContext) {
workflowStatusModelPreFilterContributor.contribute(
booleanFilter, modelSearchSettings, searchContext);
}
@Reference(target = "(model.pre.filter.contributor.id=WorkflowStatus)")
protected ModelPreFilterContributor workflowStatusModelPreFilterContributor;
}
\end{verbatim}
\textbf{Extension Point (SPI):}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/query/contributor/QueryPreFilterContributor.java}{\texttt{com.liferay.portal.search.spi.model.query.contributor.QueryPreFilterContributor}}
To contribute Filter clauses to every search, regardless of what base
entity is being searched, implement a
\texttt{QueryPreFilterContributor}. \texttt{QueryPreFilterContributor}
is constructed only once under the root filter during a search. For
example, see
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search/src/main/java/com/liferay/portal/search/internal/contributor/query/AssetCategoryTitlesKeywordQueryContributor.java}{\texttt{AssetCategoryTitlesKeywordQueryContributor}}.
What's the difference between \texttt{QueryPreFilterContributor} and
\texttt{ModelPreFilterContributor}? \texttt{QueryPreFilterContributor}
is constructed only once under the root filter during a search, while
\texttt{ModelPreFilterContributor} is constructed once per model entity
and added under each specific entity's sub-filter.
\chapter{Returning Results}\label{returning-results}
When a model entity's indexed search document is obtained during a
search request, it's converted into a summary of the model entity. You
can exert control over your model entity's summary.
\section{Creating a Results Summary}\label{creating-a-results-summary}
\texttt{ModelSummaryContributor} classes get the \texttt{Summary} object
created for each search document, so you can manipulate it by adding
specific fields or setting the length of the displayed content.
\textbf{Extension Pointi (SPI):}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/result/contributor/ModelSummaryContributor.java}{\texttt{com.liferay.portal.search.spi.model.result.contributor.ModelSummaryContributor}}
\begin{verbatim}
@Component(
immediate = true,
property = "indexer.class.name=com.liferay.foo.model.FooEntry",
service = ModelSummaryContributor.class
)
public class FooEntryModelSummaryContributor
implements ModelSummaryContributor {
@Override
public Summary getSummary(
Document document, Locale locale, String snippet) {
String languageId = LocaleUtil.toLanguageId(locale);
return _createSummary(
document,
LocalizationUtil.getLocalizedName(Field.CONTENT, languageId),
LocalizationUtil.getLocalizedName(Field.TITLE, languageId));
}
private Summary _createSummary(
Document document, String contentField, String titleField) {
String prefix = Field.SNIPPET + StringPool.UNDERLINE;
Summary summary = new Summary(
document.get(prefix + titleField, titleField),
document.get(prefix + contentField, contentField));
summary.setMaxContentLength(200);
return summary;
}
}
\end{verbatim}
\section{Controlling the Visibility of Model
Entities}\label{controlling-the-visibility-of-model-entities}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/result/contributor/ModelVisibilityContributor.java}{\texttt{ModelVisibilityContributor}}
classes control the visibility of model entities that can be attached to
other asset types (for example, File Entries can be attached to Wiki
Pages), in the search context.
\textbf{Extension Point (SPI):}
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/result/contributor/ModelVisibilityContributor.java}{\texttt{com.liferay.portal.search.spi.model.result.contributor.ModelVisibilityContributor}}
\begin{verbatim}
@Component(
immediate = true,
property = "indexer.class.name=com.liferay.foo.model.FooEntry",
service = ModelVisibilityContributor.class
)
public class FooEntryModelVisibilityContributor
implements ModelVisibilityContributor {
@Override
public boolean isVisible(long classPK, int status) {
try {
FooEntry entry = fooEntryLocalService.getEntry(classPK);
return isVisible(entry.getStatus(), status);
}
catch (PortalException pe) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to check visibility for foo entry ", pe);
}
}
return false;
}
protected boolean isVisible(int entryStatus, int queryStatus) {
if (((queryStatus != WorkflowConstants.STATUS_ANY) &&
(entryStatus == queryStatus)) ||
(entryStatus != WorkflowConstants.STATUS_IN_TRASH)) {
return true;
}
return false;
}
@Reference
protected FooEntryLocalService fooEntryLocalService;
private static final Log _log = LogFactoryUtil.getLog(
FooEntryModelVisibilityContributor.class);
}
\end{verbatim}
Once you index model entities, add their terms to the Liferay DXP search
query, and get the summary just right, your model entity is ready to be
searched.
\chapter{Search Service Registration}\label{search-service-registration}
The search framework must know about your entity and how to handle it
during a search request. To register model entities with Liferay's
search framework, \texttt{SearchRegistrar}s use the
\href{https://github.com/liferay/liferay-portal/tree/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/registrar}{search
framework's registry} to define certain things about your model entity's
\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-search/portal-search-spi/src/main/java/com/liferay/portal/search/spi/model/registrar/ModelSearchDefinition.java}{\texttt{ModelSearchDefinition}}:
the default fields used to retrieve documents from the search engine,
and the optional search services registered for your entity (for
example, the \texttt{ModelIndexWriterContributor} for you entity).
Registration occurs as soon as the Component is activated (during portal
startup or deployment of the bundle).
A Registrar is required so the container knows about your
implementation.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
First, declare the class a component and create the class declaration:
\begin{verbatim}
@Component(immediate = true, service = {})
public class FooEntrySearchRegistrar {
\end{verbatim}
\item
Next write the \texttt{activate} method, annotated with the OSGi
annotation \texttt{@Activate}. On activation of this component, call
the \texttt{ModelSearchRegistrarHelper.register} method and use the
call to build out a \texttt{ModelSearchDefinition}:
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
_serviceRegistration = modelSearchRegistrarHelper.register(
FooEntry.class, bundleContext,
modelSearchDefinition -> {
modelSearchDefinition.setDefaultSelectedFieldNames(
Field.ASSET_TAG_NAMES, Field.COMPANY_ID,
Field.ENTRY_CLASS_NAME, Field.ENTRY_CLASS_PK,
Field.GROUP_ID, Field.MODIFIED_DATE, Field.SCOPE_GROUP_ID,
Field.UID);
modelSearchDefinition.setDefaultSelectedLocalizedFieldNames(
Field.CONTENT, Field.TITLE);
modelSearchDefinition.setModelIndexWriteContributor(
modelIndexWriterContributor);
modelSearchDefinition.setModelSummaryContributor(
modelSummaryContributor);
modelSearchDefinition.setModelVisibilityContributor(
modelVisibilityContributor);
});
}
\end{verbatim}
On activation of the Component, a chain of search and indexing classes
is registered for the Foo entity. In addition, set the default
selected field names used to retrieve results documents from the
search engine. Then set the contributors used to build a model search
definition.
In addition to the \texttt{ModelSearchDefinition} setter methods used
in the above code, there's another to be aware of:
To select all locales all the time when searching for your model
entity, pass \texttt{true} to \texttt{setSelectAllLocales}:
\begin{verbatim}
modelSearchDefinition.setSelectAllLocales(true);
\end{verbatim}
Technically, there's another setter in \texttt{ModelSearchDefinition}
that takes a boolean,
\texttt{setSearchResultPermissionFilterSuppressed}. However, this is
intended for internal consumption.
\item
Write a corresponding \texttt{deactivate} method:
\begin{verbatim}
@Deactivate
protected void deactivate() {
_serviceRegistration.unregister();
}
\end{verbatim}
\item
Get references to the services needed in the class. For the search
services you're providing, specify them by entering the FQCN of your
model entity in the reference target's \texttt{indexer.class.name}
property:
\begin{verbatim}
@Reference(
target = "(indexer.class.name=com.liferay.foo.model.FooEntry)"
)
protected ModelIndexerWriterContributor
modelIndexWriterContributor;
@Reference
protected ModelSearchRegistrarHelper modelSearchRegistrarHelper;
@Reference(
target = "(indexer.class.name=com.liferay.foo.model.FooEntry)"
)
protected ModelSummaryContributor modelSummaryContributor;
@Reference(
target = "(indexer.class.name=com.liferay.foo.model.FooEntry)"
)
protected ModelVisibilityContributor modelVisibilityContributor;
private ServiceRegistration> _serviceRegistration;
\end{verbatim}
\end{enumerate}
It's quite possible you'll want to write this class after first getting
all the search and indexing logic into place. How can you register a
\texttt{ModelIndexerWriterContributor} if you haven't written one yet?
\chapter{Search Queries and Filters}\label{search-queries-and-filters}
To get sensible results from the search engine, you must provide a
sensible query.
\section{Queries and Filters in Liferay's Search
API}\label{queries-and-filters-in-liferays-search-api}
Elasticsearch and Solr do not make API level distinctions between
queries and filters. In the past, Liferay's API explicitly provided two
sets of APIs, one for queries and one for filters. Both APIs lived in
\texttt{portal-kernel} (the 7.1 source code for filters is
\href{https://github.com/liferay/liferay-portal/tree/7.1.x/portal-kernel/src/com/liferay/portal/kernel/search/filter}{here}).
In 7.0, there's a new way of querying and filtering via Liferay's Search
API, and the APIs for it live in the \texttt{portal-search-api} module.
Instead of calling specific filter APIs, you'll now construct a query
and add it to the search request, specifying it as a filter using the
\texttt{SearchRequestBuilder.postFilterQuery(Query)} method. See the
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/query}{7.2
Query APIs}.
\noindent\hrulefill
\textbf{Note}: Support for the legacy
\texttt{com.liferay.portal.kernel.search.Query.getPreBooleanFilter()} is
only present in the new search request builder and assembler
implementation to allow for backwards compatibility with the
\texttt{Indexer} framework's handling of queries. The older approach
encourages some practices that are not ideal:
\begin{itemize}
\item
Wrapping a \texttt{BooleanQuery} with another \texttt{BooleanQuery}.
\item
Some queries shouldn't have filters according to Elasticsearch's API.
\end{itemize}
\noindent\hrulefill
Despite the more unified filtering and querying code, you should
understand the functional difference between filtering and querying:
\emph{Filters} ask a yes or no question for every document. A filter
might ask \emph{is the status field equal to staging or live?}
\emph{Queries} ask the same yes or no question AND how well a document
matches the specified criteria. This is the concept of
\href{https://www.elastic.co/guide/en/elasticsearch/guide/current/scoring-theory.html}{relevance
scoring}. A query might ask \emph{Does the document's content field
field contain the words ``Liferay'', ``Content'', or ``Management'', and
how relevant is the content of the document to the searched keywords?}
\noindent\hrulefill
\textbf{Hint:} Filtering is faster than querying, since the documents
matching a filter can be cached. Queries not only match documents but
also calculate scores. We recommend using filtering and querying
together: filters to reduce the number of matched documents, queries for
the final examination.
\noindent\hrulefill
\section{Supported Query Types}\label{supported-query-types}
Liferay's Search API supports the following types of queries:
\section{Full Text Queries}\label{full-text-queries}
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/full-text-queries.html}{Full
text queries} are high-level queries usually used for querying full text
fields like the \texttt{content} field of a Blogs Entry. How terms are
matched depends on the query type.
\emph{Supported Full Text Queries}
\begin{verbatim}
CommonTermsQuery
MatchPhraseQuery
MatchPhrasePrefixQuery
MatchQuery
MultiMatchQuery
SimpleStringQuery
StringQuery
\end{verbatim}
Here are some common full text queries:
\begin{itemize}
\tightlist
\item
Match Query: A full text query, scored by relevance.
\item
Multi Match Query: Execute a \texttt{MatchQuery} over several fields.
\item
String Query: Use Lucene query syntax.
\end{itemize}
\section{Term Queries}\label{term-queries}
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/term-level-queries.html}{Term
queries} look for exact matching on keyword fields and indexed terms.
\begin{verbatim}
ExistsQuery
FuzzyQuery
IdsQuery
PrefixQuery
RangeQuery
RegexpQuery
TermQuery
TermsQuery
TermRangeQuery
TermsSetQuery
WildcardQuery
\end{verbatim}
Here are some common term queries:
\begin{itemize}
\tightlist
\item
Wildcard Query: Wildcard (\texttt{*} and \texttt{?}) matching on
keyword fields and indexed terms
\item
Fuzzy Query: Scrambles characters in input before matching
\end{itemize}
\section{Compound Queries}\label{compound-queries}
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/compound-queries.html}{Compound
queries} are often used to wrap other queries. They're commonly used to
switch from query to filter context.
\begin{verbatim}
BooleanQuery
BoostingQuery
ConstantScoreQuery
DisMaxQuery
FunctionScoreQuery
\end{verbatim}
Here are some common compound queries:
\begin{itemize}
\tightlist
\item
Boolean Query: Allows a combo of several query types. Individual
queries are as clauses with \texttt{SHOULD} \textbar{} \texttt{MUST}
\textbar{} \texttt{MUST\_NOT} \textbar{} \texttt{FILTER}
\item
Constant Score Query: Wraps another query, switches it to filter mode,
and gives all returned documents a constant relevance score.
\end{itemize}
\section{Joining Queries}\label{joining-queries}
The concept of a join doesn't work well in a distributed index.
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/joining-queries.html}{Joining
queries} allow similar behavior in the search index, such as using the
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/nested.html}{\texttt{nested}
datatype} to index an array of objects that can be queried
independently, using the \texttt{NestedQuery}.
\begin{verbatim}
NestedQuery
\end{verbatim}
Nested Query: Query nested objects as if they each had a separate
document in the index.
\section{Geo Queries}\label{geo-queries}
In Elasticsearch, you can index latitude/longitude pairs and geographic
shapes.
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/geo-queries.html}{Geo
queries} let you query for these points and shapes.
\begin{verbatim}
GeoBoundingBoxQuery
GeoDistanceQuery
GeoDistanceRangeQuery
GeoPolygonQuery
GeoShapeQuery
\end{verbatim}
A common Geo Query is the \texttt{GeoDistanceQuery}, used to find
documents within a certain distance of a geographic point
(latitude/longitude).
\section{Specialized Queries}\label{specialized-queries}
These
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/specialized-queries.html}{queries}
don't fit into another group:
\begin{verbatim}
MoreLikeThisQuery
PercolateQuery
ScriptQuery
\end{verbatim}
\begin{itemize}
\tightlist
\item
\textbf{More Like This:} Use a document to query for similar
documents.
\item
\textbf{Percolate:} Match individual documents against indexed queries
(for alerting to new documents of interest, or automatically
categorizing documents).
\item
\textbf{Script:} Filter based on a script.
\end{itemize}
\section{Other Queries}\label{other-queries}
\texttt{MatchAllQuery} Matches all documents in the index.
The proper search query is entirely context- and search engine-specific,
so you should read the Query documentation straight from
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl.html}{Elasticsearch}
or
\href{https://lucene.apache.org/solr/guide/7_1/query-syntax-and-parsing.html}{Solr}
to determine which queries are available and what they do.
All the recommended and supported queries and filters are found in the
\texttt{portal-search-api} module's
\texttt{com.liferay.portal.search.query} and
\texttt{com.liferay.portal.search.filter} packages.
Legacy queries and filters, which are still supported but moving towards
deprecation, are found in the
\texttt{com.liferay.portal.kernel.search.*} packages provided by
\texttt{portal-kernel}.
\section{Using Queries}\label{using-queries}
Here's the generalized approach for querying and filtering search
documents in your own search code:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Instantiate and construct the query object.
\item
Add the query to the search request---the method you use determines
whether the context is filtering or querying (or both in the same
request).
\item
Execute the search request.
\item
Process the search response.
\end{enumerate}
These steps are covered in more detail (with examples)
\href{/docs/7-2/frameworks/-/knowledge_base/f/building-search-queries-and-filters}{in
the next article}.
\section{Search Queries in Liferay's
Code}\label{search-queries-in-liferays-code}
The APIs for creating queries are best exemplified in Liferay's own test
cases. For example,
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-search/portal-search-test-util/src/main/java/com/liferay/portal/search/test/util/query/BaseTermsQueryTestCase.java}{BaseTermsQueryTestCase}
constructs a search request containing a \texttt{TermsQuery} using the
\texttt{Queries} API:
\begin{verbatim}
TermsQuery termsQuery = queries.terms(Field.USER_NAME);
\end{verbatim}
This code executes the search request with the terms query constructed
above in a query context.
Other query test cases are also available to reference in the
\texttt{portal-search} module's
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/portal-search/portal-search-test-util/src/main/java/com/liferay/portal/search/test/util/query}{source
code}.
\section{External References}\label{external-references-2}
\begin{itemize}
\tightlist
\item
\url{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl.html}
\item
\url{https://lucene.apache.org/solr/guide/7_1/query-syntax-and-parsing.html}
\end{itemize}
\section{Search Engine Connector
Support}\label{search-engine-connector-support-2}
\begin{itemize}
\tightlist
\item
Elasticsearch 6: Yes
\item
Solr 7: No* (Only the ``legacy'' query types from
\texttt{com.liferay.portal.kernel.search.*} are supported as of
Liferay DXP Beta 2.)
\end{itemize}
\section{New/Related APIs}\label{newrelated-apis-2}
Package \textbar{} Provided by Artifact \textbar{} Notes \textbar{}
\texttt{com.liferay.portal.search.query.*} \textbar{}
com.liferay.portal.search.api \textbar{} Most of the provided
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/query}{query
types} are new as of 7.2 \texttt{com.liferay.portal.search.filter.*}
\textbar{} com.liferay.portal.search.api \textbar{} Some of the provided
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/filter}{filter
types} are new as of 7.2
\chapter{Building Search Queries and
Filters}\label{building-search-queries-and-filters}
Each filter and query has a
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl.html}{different
purpose}, but the way you'll add the information to the search request
is similar between all queries and filters.
\section{Queries}\label{queries}
A mostly-complete code snippet for building Queries is provided for your
copying and pasting convenience \hyperref[example]{below}.
\section{Declare Gradle Dependencies}\label{declare-gradle-dependencies}
Add the following to your \texttt{build.gradle} file:
\begin{verbatim}
dependencies {
compileOnly group: "biz.aQute.bnd", name: "biz.aQute.bndlib", version: "3.5.0"
compileOnly group: "com.liferay.portal", name: "release.portal.api", version: "7.2.0"
compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
}
\end{verbatim}
With this you can import all the Search APIs.
\section{Reference the Search
Services}\label{reference-the-search-services}
To satisfy the dependencies of the example code presented here, get
references to
\begin{itemize}
\tightlist
\item
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilderFactory}
\item
\texttt{com.liferay.portal.search.searcher.Searcher}
\item
\texttt{com.liferay.portal.search.query.Queries}
\end{itemize}
\begin{verbatim}
@Reference
protected Queries queries;
@Reference
protected Searcher searcher;
@Reference
protected SearchRequestBuilderFactory searchRequestBuilderFactory;
\end{verbatim}
\section{Build the Search Query}\label{build-the-search-query-1}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use the \texttt{com.liferay.portal.search.query.Queries} interface to
instantiate the queries you'll construct. For example,
\begin{verbatim}
TermsQuery termsQuery = queries.terms("fieldName");
MatchQuery matchQuery = queries.match("fieldName", "value");
BooleanQuery booleanQuery = queries.booleanQuery();
\end{verbatim}
To discover what parameters each query must have (e.g.,
\texttt{String\ field} in the case of the above
\texttt{com.liferay.portal.search.query.TermsQuery}), see the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-search/portal-search-api/src/main/java/com/liferay/portal/search/query/Queries.java}{\texttt{Queries}}
interface.
\item
Build out the queries to get the desired response. This looks
different for each query type, as explained in
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl.html}{Elasticsearch's
Query documentation}.
\begin{verbatim}
termsQuery.addValues("value1", "value2");
\end{verbatim}
\item
You might want to wrap queries. For example, use the queries
constructed above as MUST clauses in a \texttt{BooleanQuery} wrapper:
\begin{verbatim}
booleanQuery.addMustQueryClauses(termsQuery, matchQuery);
\end{verbatim}
\end{enumerate}
Once the query itself is in good shape, feed it to the search request.
\section{Build the Search Request}\label{build-the-search-request}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilderFactory}
to get an instance of
\texttt{com.liferay.portal.search.searcher.SearchRequestBuilder}:
\begin{verbatim}
SearchRequestBuilder searchRequestBuilder =
searchRequestBuilderFactory.builder();
\end{verbatim}
If not setting search keywords into the \texttt{SearchContext}
(covered below), make sure the request builder enables empty search.
\begin{verbatim}
searchRequestBuilder.emptySearchEnabled(true);
\end{verbatim}
Set the \texttt{long\ companyId} and, optionally,
\texttt{String\ keywords} into the
\texttt{com.liferay.portal.kernel.search.SearchContext}:
\begin{verbatim}
searchRequestBuilder.withSearchContext(
searchContext -> {
searchContext.setCompanyId(companyId);
searchContext.setKeywords(keywords);
});
\end{verbatim}
Setting the Company ID into the \texttt{SearchContext} is required to
ensure the correct index is searched.
Setting ``keywords'' on the \texttt{SearchContext} is necessary if you
want to search via user input. For example, if providing a Search bar
in an application's view layer, pass its input into the search
context. Liferay's search framework adds the user input keywords and
any other data in the \texttt{SearchContext} object to its own
queries, searching the appropriate fields of each indexed entity, as
defined by its
\href{/docs/7-2/frameworks/-/knowledge_base/f/searching-the-index-for-model-entities\#adding-your-model-entitys-terms-to-the-query}{\texttt{KeywordQueryContributor}}
or by the \texttt{postProcessSearchQuery} method of its
\texttt{Indexer}.
\item
To execute the query, get a
\texttt{com.liferay.portal.search.searcher.SearchRequest} instance
from the builder by adding the query to it and running its
\texttt{build} method:
\begin{verbatim}
SearchRequest searchRequest =
searchRequestBuilder.query(booleanQuery).build();
\end{verbatim}
\item
To use a constructed query in a filter context, call the
\texttt{postFilterQuery} method while building the request:
\begin{verbatim}
SearchRequest searchRequest =
searchRequestBuilder.postFilterQuery(termsQuery).build();
\end{verbatim}
\item
When constructing a search request, you'll often find it necessary to
chain the builder methods together:
\begin{verbatim}
SearchRequest searchRequest =
searchRequestBuilder.postFilterQuery(myQuery1).query(myQuery2).build();
\end{verbatim}
Chaining allows you to add filters and queries (and anything else from
the builder API) to the same request in one fell swoop.
\end{enumerate}
\section{Execute the Search Request}\label{execute-the-search-request}
Perform a search using the
\texttt{com.liferay.portal.search.searcher.Searcher} service and the
\texttt{SearchRequest} to get a
\texttt{com.liferay.portal.search.searcher.SearchResponse}:
\begin{verbatim}
SearchResponse searchResponse = searcher.search(searchRequest);
\end{verbatim}
\section{Process the Search Response}\label{process-the-search-response}
What you'll do with the \texttt{SearchResponse} returned by the
\texttt{searcher.search} call is dependent on the type of query and your
specific use case. Much of the time you'll want to loop through the
\texttt{com.liferay.portal.search.hits.SearchHit} and
\texttt{com.liferay.portal.search.document.Document} objects, so that's
what's shown here, with a simple message printed in the log for each
one.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get the \texttt{SearchHits} from the response:
\begin{verbatim}
SearchHits searchHits = searchResponse.getSearchHits();
\end{verbatim}
\item
Get a List of the \texttt{SearchHit} objects:
\begin{verbatim}
List searchHitsList = searchHits.getSearchHits();
\end{verbatim}
\item
Loop through the \texttt{SearchHit} objects in the List, get the
\texttt{Document} associated with each one, printing its score and UID
to the console:
\begin{verbatim}
searchHitsList.forEach(
searchHit -> {
float hitScore = searchHit.getScore();
Document doc = searchHit.getDocument();
String uid = doc.getString(Field.UID);
System.out.println(
StringBundler.concat(
"Document ", uid, " had a score of ", hitScore));
});
\end{verbatim}
\end{enumerate}
\section{Search Insights: Request and Response
Strings}\label{search-insights-request-and-response-strings}
When building a search application, it can be useful to inspect the
request string (translated into the search engine's dialect), and
subsequently see the response string returned by the search server.
Retrieve these from the \texttt{SearchResponse} as
\begin{verbatim}
searchResponse.getRequestString();
searchResponse.getResponseString();
\end{verbatim}
The format depends on your search engine: with Elasticsearch, both are
JSON.
\noindent\hrulefill
\textbf{Note:} The JSON returned as a request string is pruned from
several Elasticsearch query defaults for clarity. To see the full
request JSON that Elasticsearch processed, adjust the
\href{https://www.elastic.co/guide/en/elasticsearch/reference/6.x/logging.html}{Elasticsearch
server's logging}.
\noindent\hrulefill
Inspecting the request string produced by the code example included
\hyperref[example]{here} reveals two main \texttt{"bool":"must"} query
clauses in the JSON being sent to the search engine:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The \texttt{BooleanQuery} explicitly declared in the code example.
\item
A (very long) query determined by the logic embedded in the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-search/portal-search/src/main/java/com/liferay/portal/search/internal/searcher/SearcherImpl.java\#L137}{\texttt{SearcherImpl\#doSearch}}
method.
\end{enumerate}
How you construct the \texttt{SearchRequest} determines how the
\texttt{Searcher} API processes it, which in turn influences the request
String sent to Elasticsearch. For example, sending \texttt{keywords}
into the \texttt{SearchContext} object passed to the
\texttt{SearchRequest} ensures that queries for certain fields are
executed on all searchable documents.
\section{Queries Example}\label{queries-example}
The code below performs a \texttt{MatchQuery} on the
\texttt{title\_en\_US} field for the value provided via the
\texttt{keywords} String. In addition, a \texttt{TermsQuery} on the
\texttt{folderId} field is executed to match a value of \texttt{0} (root
\texttt{JournalFolder}s are identified by
\texttt{JournalFolderConstants.DEFAULT\_PARENT\_FOLDER\_ID}, which
evaluates to \texttt{0}). Both queries are wrapped in a
\texttt{BooleanQuery} must clause.
Because this example passes \texttt{keywords} to the
\texttt{SearchContext}, \texttt{emptySearchEnabled(true)} is not called
on the \texttt{SearchRequestBuilder}. The \texttt{keywords} variable is
not explicitly declared because this should come from user input.
Therefore the example \texttt{search} method receives \texttt{keywords}
as a parameter, along with the \texttt{companyId}:
\begin{verbatim}
package com.liferay.docs.search;
import com.liferay.petra.string.StringBundler;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.search.Field;
import com.liferay.portal.kernel.util.LocaleUtil;
import com.liferay.portal.search.document.Document;
import com.liferay.portal.search.hits.SearchHit;
import com.liferay.portal.search.hits.SearchHits;
import com.liferay.portal.search.query.BooleanQuery;
import com.liferay.portal.search.query.MatchQuery;
import com.liferay.portal.search.query.Queries;
import com.liferay.portal.search.query.TermsQuery;
import com.liferay.portal.search.searcher.SearchRequest;
import com.liferay.portal.search.searcher.SearchRequestBuilder;
import com.liferay.portal.search.searcher.SearchRequestBuilderFactory;
import com.liferay.portal.search.searcher.SearchResponse;
import com.liferay.portal.search.searcher.Searcher;
import java.util.ArrayList;
import java.util.List;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
service = MySearchComponent.class
)
public class MySearchComponent {
public List search(long companyId, String keywords)
throws PortalException {
MatchQuery titleQuery = queries.match(
Field.getLocalizedName(LocaleUtil.US, Field.TITLE), keywords);
TermsQuery rootFolderQuery = queries.terms(Field.FOLDER_ID);
rootFolderQuery.addValues(String.valueOf(
JournalFolderConstants.DEFAULT_PARENT_FOLDER_ID));
BooleanQuery booleanQuery = queries.booleanQuery();
booleanQuery.addMustQueryClauses(rootFolderQuery, titleQuery);
SearchRequestBuilder searchRequestBuilder =
searchRequestBuilderFactory.builder();
// Uncomment this line below if you aren't setting "keywords"
// on the SearchContext
// searchRequestBuilder.emptySearchEnabled(true);
searchRequestBuilder.withSearchContext(
searchContext -> {
searchContext.setCompanyId(companyId);
searchContext.setKeywords(keywords);
});
SearchRequest searchRequest = searchRequestBuilder.query(
booleanQuery
).build();
SearchResponse searchResponse = searcher.search(searchRequest);
SearchHits searchHits = searchResponse.getSearchHits();
List searchHitsList = searchHits.getSearchHits();
List resultsList = new ArrayList<>(searchHitsList.size());
searchHitsList.forEach(
searchHit -> {
float hitScore = searchHit.getScore();
Document doc = searchHit.getDocument();
String uid = doc.getString(Field.UID);
System.out.println(
StringBundler.concat(
"Document ", uid, " had a score of ", hitScore));
resultsList.add(uid);
});
System.out.println(StringPool.EIGHT_STARS);
/*
* // Uncomment to inspect the Request and Response Strings
* System.out.println( "Request String:\n" + searchResponse.getRequestString() +
* "\n" + StringPool.EIGHT_STARS);
* System.out.println( "Response String:\n" +
* searchResponse.getResponseString() + "\n" + StringPool.EIGHT_STARS);
*/
return resultsList;
}
@Reference
protected Queries queries;
@Reference
protected Searcher searcher;
@Reference
protected SearchRequestBuilderFactory searchRequestBuilderFactory;
}
\end{verbatim}
\section{Filters}\label{filters}
Filters as a distinct API-level object in Liferay DXP are going away.
It's best to mirror the APIs of the search engine, and neither supported
search engine makes an API level distinction between queries and
filters. In recognition of this, there's a new way to perform
post-filtering, which is filtering the returned search documents at the
end of the search request (after calculating any aggregations). Add the
filter to the query using the \texttt{postFilterQuery} method in the
request builder:
\begin{verbatim}
SearchRequestBuilder.postFilterQuery(Query);
\end{verbatim}
As you can see, this takes a \texttt{Query} object, not a
\texttt{Filter}. Therefore, construct the \texttt{Query} as in the
previous section, and specify it as a post-filter while building the
request. All of the legacy \texttt{Filter} objects from
\texttt{portal-kernel} can now be constructed as queries, per the above
query-building documentation.
\section{Legacy Filters in Legacy Search
Calls}\label{legacy-filters-in-legacy-search-calls}
Constructing the filters found in \texttt{portal-kernel}'s
\texttt{com.liferay.portal.kernel.search.filter} package is demonstrated
by this \texttt{new} term filter:
\begin{verbatim}
TermFilter termFilter = new TermFilter("fieldName", "filterValue");
\end{verbatim}
Filters are added in legacy search calls by going through the
\texttt{Indexer} framework's \texttt{postProcessContextBooleanFilter}
method, which is invoked while the search framework is constructing the
main search query. See the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/users-admin/users-admin-impl/src/main/java/com/liferay/users/admin/internal/search/UserIndexer.java}{\texttt{UserIndexer}'s
\texttt{addContextQueryParams} method}, which is called in the
overridden \texttt{postProcessContextBooleanFilter} to add the filter
logic.
\section{Discovering Indexed Fields}\label{discovering-indexed-fields}
To find the fields to use in your Queries, navigate to \emph{Control
Panel} → \emph{Configuration} → \emph{Search} in a running portal. From
there, open the Field Mappings tab and browse the mappings for the
\texttt{liferay-{[}companyId{]}} index. Scroll to the
\href{https://www.elastic.co/guide/en/elasticsearch/reference/current/properties.html}{\texttt{properties}}
section of the mapping.
A summary of the text fields that are localized can be found
\href{/docs/7-2/user/-/knowledge_base/u/searching-for-localized-content}{here}.
\chapter{Segmentation and
Personalization}\label{segmentation-and-personalization}
Segments are groups of users that are defined by a specific criteria.
You can use the metadata from the user or organization profile, context
information derived from the user's behavior, or some combination of the
two to define that criteria. Alternatively, segments can be a static set
of manually selected members.
In 7.0, the creation of user segments and experience personalization are
now part of the product's core functionality. Up to Liferay DXP 7.1,
this functionality was provided through the Audience Targeting
application. In addition to the administration features of Segmentation
and Personalization, developers can integrate and extend it.
\section{Managing segments}\label{managing-segments}
The API to manage segments is provided by the
\texttt{com.liferay.segments.api\ module}. The
\texttt{SegmentsEntryService} provides the methods to perform permission
aware operations on segments. You can use the provided tools to assign
members to segments and to extend segment criteria.
The \texttt{segmentsEntry} criteria field determines the conditions that
a user must meet to be assigned to the segment. A condition represents a
combination of properties, operations, target values, and conjunctions.
For example, a condition identifying Liferay Engineers for a Segment
might look like this:
\begin{verbatim}
organization name EQUALS Liferay AND Job Title EQUALS Engineer
\end{verbatim}
In the Segments UI, the segments criteria is built using the
\href{/docs/7-2/user/-/knowledge_base/u/the-segment-editor}{Segments
Editor}. The available properties are grouped by topic (e.g.~User,
Organization, Session). Technically, they are called a
\texttt{SegmentsCriteriaContributor}, because they \emph{contribute}
conditions to the segments criteria.
You can see a number of common Segment management operations with
example code in
\href{/docs/7-2/frameworks/-/knowledge_base/f/segment-management}{Segment
Management}.
\section{Extending Segment Criteria}\label{extending-segment-criteria}
The default segment capabilities are robust enough to cover most use
cases, and many types of third party integration can be performed
without developing a code extension. Some cases, like retrieving an
external segment or list can be be handled by using the REST API.
On the other hand, if you want to segment users based on a field
provided from an external source (for example, the number of followers a
user has on Instagram), you can contribute an indexable
\href{/docs/7-2/user/-/knowledge_base/u/custom-fields}{custom field} to
the User entity and query the value using the Expando API. Your new
field is automatically available for its use as a profile-based
criteria.
You must only develop a code extension if you must:
\begin{itemize}
\item
Add a custom session property. This is done through the
\texttt{RequestContextContributor}.
\item
Extend the criteria query with new queries, based either on existing
model entities or in custom model entities. This is done through the
\texttt{SegmentsCriteriaContributor}.
\end{itemize}
\section{\texorpdfstring{\texttt{RequestContextContributor}}{RequestContextContributor}}\label{requestcontextcontributor}
User and Organization properties are model-based properties. That means
that the available criteria for users and organizations are based on the
attributes for users and organizations defined by the entities
\texttt{model}. Criteria for model-based entities can be extended by
creating a Custom Field for the corresponding model. Session properties
are context-based properties and can't be extended through custom
fields. To allow for user segmentation based on new context-based
properties, like custom HTTP headers or attributes, you must develop an
extension and deploy it in your Liferay DXP instance.
The default fields available for context-based segmentation can be found
in the Context interface. Liferay generates a context instance with
real-time information for every request. These are mostly obtained from
the \texttt{HttpServletRequest}. The \texttt{RequestContextContributor}
interface provides an extension point for adding a new context-based
property to the Session panel in the Segments criteria editor and
populating the segmentation context with the right value for that field.
The following service properties define a
\texttt{RequestContextContributor}:
\texttt{request.context.contributor.key}: the unique key of the
contributed field.
\texttt{request.context.contributor.type}: the contributor field type
(boolean, date, double, integer, or string{[}default{]}).
The \texttt{contribute} method of the \texttt{RequestContextContributor}
adds the custom field key-value pair to the context.
\begin{figure}
\centering
\includegraphics{./images/request-context-contributor.png}
\caption{Learn more about a \texttt{RequestContextContributor} by
viewing how it's used.}
\end{figure}
To create a \texttt{RequestContextContributor} through the step by step
process, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-request-context-contributor}{Creating
a Request Context Contributor}.
\section{\texorpdfstring{\texttt{SegmentsCriteriaContributor}}{SegmentsCriteriaContributor}}\label{segmentscriteriacontributor}
The \texttt{SegmentsCriteriaContributor} interface provides a mechanism
to extend the segment criteria query. Each
\texttt{SegmentsCriteriaContributor} contributes a sub-query (or
criterion) and the conjunction (AND, OR) to build the complete criteria
query that defines the segment. They also provide a list of Field
elements to be shown in the Segment Editor UI, as depicted in the
figure:
\begin{figure}
\centering
\includegraphics{./images/segment-field-contributor.png}
\caption{Learn more about a \texttt{SegmentsCriteriaContributor} by
viewing how it's used.}
\end{figure}
The following service properties describe a
\texttt{SegmentsCriteriaContributor}:
\texttt{segments.criteria.contributor.key}: the unique key that
identifies the contributor.
\texttt{segments.criteria.contributor.model.class.name}: the entity type
the contributor targets.
\texttt{segments.criteria.contributor.priority}: the order in which the
fields and queries are contributed.
The \texttt{UserOrganizationSegmentsCriteriaContributor} is a good
example of how a \texttt{SegmentsCriteriaContributor} works. It
contributes new organization-related fields (\emph{Organization
Properties}) to the segments criteria editor, executes a query on the
Organization based model, and finally contributes a subquery to the
global user query (AND/OR the user belongs to the organizations found in
the Organization model query). In summary, you can filter users based on
aspects of a different but related entity, such as the organization.
To create a \texttt{SegmentsCriteriaContributor} through the step by
step process, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-segment-criteria-contributor}{Creating
Segment Criteria Contributors}.
\chapter{Segment Management}\label{segment-management}
There are a broad array of uses for Segments and ways that you can
integrate them with your application. You'll learn more about how to
manage segments next.
\section{Defining a Segment}\label{defining-a-segment}
This snippet defines a segment by retrieving \texttt{@Reference} objects
from \texttt{SegmentsCriteriaContributor} and instantiating a new
\texttt{Criteria} object. It then adds user criteria using the
\texttt{segmentsEntryService}:
\begin{itemize}
\tightlist
\item
the user's \texttt{jobTitle} is \texttt{Developer} \textbf{AND}
\item
the user belongs to an Organization with a name that contains
\texttt{America}
\end{itemize}
\begin{verbatim}
private void addSegmentWithCriteria() {
Criteria criteria = new Criteria();
_userSegmentsCriteriaContributor.contribute(
criteria, "(jobTitle eq 'Developer')", Criteria.Conjunction.AND);
_organizationCriteriaContributor.contribute(
criteria, "contains(name,'America')", Criteria.Conjunction.OR);
segmentsEntryService.addSegmentsEntry(
"segment-key", nameMap, descriptionMap, true, CriteriaSerializer.serialize(criteria),
User.class.getName(), serviceContext);
}
@Reference(target = "(segments.criteria.contributor.key=organization)")
private SegmentsCriteriaContributor _organizationSegmentsCriteriaContributor;
@Reference(target = "(segments.criteria.contributor.key=user)")
private SegmentsCriteriaContributor _userSegmentsCriteriaContributor;
\end{verbatim}
\section{Manual Segment Member
Assignments}\label{manual-segment-member-assignments}
To define manual user-segment member assignments with the
\texttt{SegmentsEntryRelService}, use a snippet like this:
\begin{verbatim}
segmentsEntryRelService.addSegmentsEntryRel(
segmentsEntryId, _portal.getClassNameId(User.class), userId, serviceContext)
\end{verbatim}
This assigns a user identified by a \texttt{userId} to a segment
identified by a \texttt{segmentsEntryId}:
\section{Retrieving Segments}\label{retrieving-segments}
Segments and Segment Members can be retrieved programmatically. The code
snippet below retrieves an ordered range of active segments for the
\texttt{User}, within a site identified by a \texttt{groupId}.
\begin{verbatim}
List segmentsEntries = segmentsEntryService.getSegmentsEntries(groupId, true, User.class.getName(), 0, 10, orderByComparator);
\end{verbatim}
\section{Retrieving segment members}\label{retrieving-segment-members}
The local API to query computed segment-member associations is available
in the \texttt{com.liferay.segments.api\ module}. The
\texttt{SegmentsEntryProvider} service provides methods to obtain the
entities associated to a segment, and the segments associated to an
entity.
This snippet retrieves a range of primary keys of the entities
associated to a segment identified by a \texttt{segmentsEntryId}:
\begin{verbatim}
long[] segmentsEntryClassPKs = segmentsEntryProvider.getSegmentsEntryClassPKs(segmentsEntryId, 0, 10);
\end{verbatim}
To obtain the total count of entities associated to a segment, use the
\texttt{getSegmentsEntryClassPKsCount} method, as shown in the following
snippet:
\begin{verbatim}
int segmentsEntryClassPksCount =
segmentsEntryProvider.getSegmentsEntryClassPKsCount(segmentsEntryId);
\end{verbatim}
The method \texttt{getSegmentsEntryIds} obtains the reverse association
--- the segments associated to a specific entity. For example, this
snippet returns the segments associated to a user identified by a
\texttt{userId}:
\begin{verbatim}
int segmentsEntryClassPksCount =
segmentsEntryProvider.getSegmentsEntryIds(User.class.getName(), userId);
\end{verbatim}
Great! You now know how to manage segments!
\chapter{Creating a Request Context
Contributor}\label{creating-a-request-context-contributor}
To better understand the Request Context Contributor, you'll explore how
to create one. First, you'll create the
\texttt{SampleRequestContentContributor} class file, which contains the
\texttt{contribute} method that contributes a new field to the context
with a custom attribute. You can view the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/segments/segments-context-extension-sample}{full
project on Github}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a new module}.
\item
Inside the module, create a package named
\texttt{com.liferay.segments.context.extension.sample.internal.context.contributor}
\item
Create a Java class named \texttt{SampleRequestContentContributor}.
\item
Inside the file, insert the \texttt{@Component} declaration:
\begin{verbatim}
@Component(
immediate = true,
property = {
"request.context.contributor.key=" + SampleRequestContextContributor.KEY,
"request.context.contributor.type=boolean"
},
service = RequestContextContributor.class
)
\end{verbatim}
\item
Add the class declaration:
\begin{verbatim}
public class SampleRequestContextContributor
implements RequestContextContributor {
}
\end{verbatim}
\item
Create the attribute that you're adding. In this case, it's just a
static string.
\begin{verbatim}
public static final String KEY = "sample";
\end{verbatim}
\item
Create the \texttt{contribute} method:
\begin{verbatim}
@Override
public void contribute(
Context context, HttpServletRequest httpServletRequest) {
context.put(KEY,
GetterUtil.getBoolean(httpServletRequest.getAttribute("sample.attribute")));
}
\end{verbatim}
\end{enumerate}
To customize your field label or add a set of selectable options, you
can add an optional \texttt{SegmentsFieldCustomizer} service associated
to your contributed field by its key. Create one now.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the module, create a package named
\texttt{com.liferay.context.extension.sample.internal.field.customizer}
\item
Create a Java class named \texttt{SampleSegmentsFieldCustomizer}.
\item
Inside the file, insert the \texttt{@Component} declaration:
\begin{verbatim}
@Component(
immediate = true,
property = {
"segments.field.customizer.entity.name=Context",
"segments.field.customizer.key=" + SampleSegmentsFieldCustomizer.KEY,
"segments.field.customizer.priority:Integer=50"
},
service = SegmentsFieldCustomizer.class
)
\end{verbatim}
\item
Create the class declaration:
\begin{verbatim}
public class SampleSegmentsFieldCustomizer implements SegmentsFieldCustomizer {
}
\end{verbatim}
\item
Create the \texttt{KEY} value:
\begin{verbatim}
public static final String KEY = "sample";
\end{verbatim}
\item
Create the methods to provide a list of fields to be displayed when
configuring the criteria.
\begin{verbatim}
@Override
public List getFieldNames() {
return _fieldNames;
}
@Override
public String getKey() {
return KEY;
}
@Override
public String getLabel(String fieldName, Locale locale) {
ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
"content.Language", locale, getClass());
return LanguageUtil.get(resourceBundle, "sample-field-label");
}
private static final List _fieldNames = ListUtil.fromArray(
new String[] {"sample"});
\end{verbatim}
\end{enumerate}
Once you deploy your extensions, the session section of the segment
criteria editor includes your new context-based field.
\begin{figure}
\centering
\includegraphics{./images/context-based-field.png}
\caption{The sample field appears.}
\end{figure}
Great! You've created a Request Context Contributor!
\chapter{Creating a Segment Criteria
Contributor}\label{creating-a-segment-criteria-contributor}
To demonstrate the Segment Criteria Contributor, you'll create a
contributor that segments users based on the title of Knowledge Base
articles they have authored.
The first step is to make your related entity searchable through OData
queries. For this purpose, you must have classes:
\begin{itemize}
\item
\texttt{EntityModel}: represents your associated entity (in this case,
the \texttt{KBArticle}) with its fields of interest.
\item
\texttt{ODataRetriever}: obtains the \texttt{KBArticles} that match a
given OData query.
\end{itemize}
You can view the
\href{https://github.com/epgarcia/liferay-portal/tree/LPS-86249.criteria.extension.sample.2/modules/apps/segments/segments-criteria-extension-sample}{full
project on Github}.
Follow the instructions below to get started.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a module}.
\item
Create the following packages within the module:
\begin{itemize}
\tightlist
\item
\texttt{com.liferay.segments.criteria.extension.sample.internal.odata.retreiver}
\item
\texttt{com.liferay.segments.criteria.extension.sample.internal.odata.entity}
\item
\texttt{com.liferay.segments.criteria.extension.sample.internal.criteria.contributor}
\end{itemize}
\end{enumerate}
Excellent! You have your module ready. Next, you'll create the entity
model.
\section{Creating the Entity Model}\label{creating-the-entity-model}
First, create the Entity Model for \texttt{KBArticle}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the \texttt{...internal.odata.entity} package, create the
\texttt{KBArticleEntityModel} class which implements
\texttt{EntityModel}:
\begin{verbatim}
public class KBArticleEntityModel implements EntityModel {
}
\end{verbatim}
\item
Create the key for the entity name:
\begin{verbatim}
public static final String NAME = "KBArticle";
\end{verbatim}
\item
Create the variable for the entity field map:
\begin{verbatim}
private final Map _entityFieldsMap;
\end{verbatim}
\item
Create the methods to retrieve the \texttt{KBArticleEntity}, entity
map, and entity name key:
\begin{verbatim}
public KBArticleEntityModel() {
_entityFieldsMap = Stream.of(
new StringEntityField("title", locale -> "titleKeyword")
).collect(
Collectors.toMap(EntityField::getName, Function.identity())
);
}
@Override
public Map getEntityFieldsMap() {
return _entityFieldsMap;
}
@Override
public String getName() {
return NAME;
}
\end{verbatim}
\end{enumerate}
Next, you'll create the OData Retriever.
\section{\texorpdfstring{Creating the
\texttt{ODataRetriever}}{Creating the ODataRetriever}}\label{creating-the-odataretriever}
Next, create the \texttt{ODataRetriever} which gets the data using the
relevant filter.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the \texttt{...internal.odata.retreiver} package, create
\texttt{KBArticleODataRetriever.java} which implements
\texttt{ODataRetriever}:
\begin{verbatim}
public class KBArticleODataRetriever implements ODataRetriever {
}
\end{verbatim}
\item
Add the \texttt{@Component} declaration above the class declaration:
\begin{verbatim}
@Component(
immediate = true,
property = "model.class.name=com.liferay.knowledge.base.model.KBArticle",
service = ODataRetriever.class
)
\end{verbatim}
\item
Create the \texttt{@Reference} objects that you need for the Filter
Parser, Knowledge Base Article Service, and OData Search Adapter:
\begin{verbatim}
@Reference
private FilterParserProvider _filterParserProvider;
@Reference
private KBArticleLocalService _kbArticleLocalService;
@Reference
private ODataSearchAdapter _oDataSearchAdapter;
\end{verbatim}
\item
Create and instantiate the \texttt{\_entityModel} object for the
\texttt{KBArticle} model:
\begin{verbatim}
private static final EntityModel _entityModel = new KBArticleEntityModel();
\end{verbatim}
\item
Create the public methods to retrieve the results and the results
count from the OData filter:
\begin{verbatim}
@Override
public List getResults(
long companyId, String filterString, Locale locale, int start, int end)
throws PortalException {
Hits hits = _oDataSearchAdapter.search(
companyId, filterString, KBArticle.class.getName(), _entityModel,
_getFilterParser(), locale, start, end);
return _getKBArticles(hits);
}
@Override
public int getResultsCount(
long companyId, String filterString, Locale locale)
throws PortalException {
return _oDataSearchAdapter.searchCount(
companyId, filterString, KBArticle.class.getName(), _entityModel,
_getFilterParser(), locale);
}
\end{verbatim}
\item
Create the private methods for instantiating the \texttt{FilterParser}
and retrieving the Knowledge Base article(s) that meet the criteria:
\begin{verbatim}
private FilterParser _getFilterParser() {
return _filterParserProvider.provide(_entityModel);
}
private KBArticle _getKBArticle(Document document) throws PortalException {
long resourcePrimKey = GetterUtil.getLong(
document.get(Field.ENTRY_CLASS_PK));
return _kbArticleLocalService.getLatestKBArticle(resourcePrimKey, 0);
}
private List _getKBArticles(Hits hits) throws PortalException {
Document[] documents = hits.getDocs();
List kbArticles = new ArrayList<>(documents.length);
for (Document document : documents) {
kbArticles.add(_getKBArticle(document));
}
return kbArticles;
}
\end{verbatim}
\end{enumerate}
You're all set to create the Segments Criteria Contributor!
\section{\texorpdfstring{Creating the
\texttt{SegmentsCriteriaContributor}}{Creating the SegmentsCriteriaContributor}}\label{creating-the-segmentscriteriacontributor}
Now create the \texttt{SegmentsCriteriaContributor} class that consumes
the previous classes to retrieve the articles that match the query
generated by the criteria editor, and contributes a query to filter
users based on the articles they authored.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the \texttt{...internal.criteria.contributor} package, create a
\texttt{UserKBArticleSegmentCritieriaContributor} class that
implements \texttt{SegmentsCriteriaContributor}.
\begin{verbatim}
public class UserKBArticleSegmentsCriteriaContributor
implements SegmentsCriteriaContributor {
}
\end{verbatim}
\item
Create the \texttt{@Component} declaration to set properties and
declare the service class.
\begin{verbatim}
@Component(
immediate = true,
property = {
"segments.criteria.contributor.key=" + UserKBArticleSegmentsCriteriaContributor.KEY,
"segments.criteria.contributor.model.class.name=com.liferay.portal.kernel.model.User",
"segments.criteria.contributor.priority:Integer=70"
},
service = SegmentsCriteriaContributor.class
)
\end{verbatim}
\item
Create the variables to enable logging, retrieve the entity model, and
entity key.
\begin{verbatim}
private static final Log _log = LogFactoryUtil.getLog(
UserKBArticleSegmentsCriteriaContributor.class);
private static final EntityModel _entityModel = new KBArticleEntityModel();
public static final String KEY = "user-kb-article";
\end{verbatim}
\item
Create the reference variables for the OData retriever and Portal
instance.
\begin{verbatim}
@Reference(
target = "(model.class.name=com.liferay.knowledge.base.model.KBArticle)"
)
private ODataRetriever _oDataRetriever;
@Reference
private Portal _portal;
\end{verbatim}
\item
Create the methods to define the implementation of
\texttt{SegmentsCriteriaContributor}.
\begin{verbatim}
@Override
public void contribute(
Criteria criteria, String filterString,
Criteria.Conjunction conjunction) {
criteria.addCriterion(getKey(), getType(), filterString, conjunction);
long companyId = CompanyThreadLocal.getCompanyId();
String newFilterString = null;
try {
StringBundler sb = new StringBundler();
List kbArticles = _oDataRetriever.getResults(
companyId, filterString, LocaleUtil.getDefault(),
QueryUtil.ALL_POS, QueryUtil.ALL_POS);
for (int i = 0; i < kbArticles.size(); i++) {
KBArticle kbArticle = kbArticles.get(i);
sb.append("(userId eq '");
sb.append(kbArticle.getUserId());
sb.append("')");
if (i < (kbArticles.size() - 1)) {
sb.append(" or ");
}
}
newFilterString = sb.toString();
}
catch (PortalException pe) {
_log.error(
com.liferay.petra.string.StringBundler.concat(
"Unable to evaluate criteria ", criteria, " with filter ",
filterString, " and conjunction ", conjunction.getValue()),
pe);
}
if (Validator.isNull(newFilterString)) {
newFilterString = "(userId eq '0')";
}
criteria.addFilter(getType(), newFilterString, conjunction);
}
@Override
public EntityModel getEntityModel() {
return _entityModel;
}
@Override
public String getEntityName() {
return KBArticleEntityModel.NAME;
}
@Override
public List getFields(PortletRequest portletRequest) {
return Collections.singletonList(
new Field(
"title",
LanguageUtil.get(_portal.getLocale(portletRequest), "title"),
"string"));
}
@Override
public String getKey() {
return KEY;
}
@Override
public Criteria.Type getType() {
return Criteria.Type.MODEL;
}
\end{verbatim}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy
your module}.
\end{enumerate}
After deploying your extension, the segment criteria editor includes a
new section containing Knowledge Base properties. Notice that the
section's UI, the properties, and their associated input fields and
operations have been automatically generated based on the information
provided by the extension services. For instance, the Knowledge Base
article title supports \emph{equals}, \emph{not equals},
\emph{contains}, and \emph{not contains} operations because it was
defined as a \texttt{StringEntityField}.
\begin{figure}
\centering
\includegraphics{./images/segment-new-category.png}
\caption{The sample field appears.}
\end{figure}
Awesome! You've created a Segment Criteria Contributor!
\chapter{ServiceContext}\label{servicecontext}
The \texttt{ServiceContext} class holds contextual information for a
service. It aggregates information necessary for features used
throughout Liferay's portlets, such as permissions, tagging,
categorization, and more. This article covers the following
\texttt{ServiceContext} class topics:
\begin{itemize}
\tightlist
\item
\hyperref[service-context-fields]{Service Context Fields}
\item
\hyperref[creating-and-populating-a-service-context]{Creating and
Populating a Service Context in Java}
\item
\hyperref[creating-and-populating-a-service-context-in-javascript]{Creating
and Populating a Service Context in JavaScript}
\item
\hyperref[accessing-service-context-data]{Accessing Service Context
Data}
\end{itemize}
The \texttt{ServiceContext} fields are first.
\section{Service Context Fields}\label{service-context-fields}
The \texttt{ServiceContext} class has many fields. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/ServiceContext.html}{\texttt{ServiceContext}
class Javadoc} describes them.
Here's a categorical listing of some commonly used Service Context
fields:
\begin{itemize}
\tightlist
\item
Actions:
\begin{itemize}
\tightlist
\item
\texttt{\_command}
\item
\texttt{\_workflowAction}
\end{itemize}
\item
Attributes:
\begin{itemize}
\tightlist
\item
\texttt{\_attributes}
\item
\texttt{\_expandoBridgeAttributes}
\end{itemize}
\item
Classification:
\begin{itemize}
\tightlist
\item
\texttt{\_assetCategoryIds}
\item
\texttt{\_assetTagNames}
\end{itemize}
\item
Exception
\begin{itemize}
\tightlist
\item
\texttt{\_failOnPortalException}
\end{itemize}
\item
IDs and Scope:
\begin{itemize}
\tightlist
\item
\texttt{\_companyId}
\item
\texttt{\_portletPreferencesIds}
\item
\texttt{\_plid}
\item
\texttt{\_scopeGroupId}
\item
\texttt{\_userId}
\item
\texttt{\_uuid}
\end{itemize}
\item
Language:
\begin{itemize}
\tightlist
\item
\texttt{\_languageId}
\end{itemize}
\item
Miscellaneous:
\begin{itemize}
\tightlist
\item
\texttt{\_headers}
\item
\texttt{\_signedIn}
\end{itemize}
\item
Permissions:
\begin{itemize}
\tightlist
\item
\texttt{\_addGroupPermissions}
\item
\texttt{\_addGuestPermissions}
\item
\texttt{\_deriveDefaultPermissions}
\item
\texttt{\_modelPermissions}
\end{itemize}
\item
Request
\begin{itemize}
\tightlist
\item
\texttt{\_request}
\end{itemize}
\item
Resources:
\begin{itemize}
\tightlist
\item
\texttt{\_assetEntryVisible}
\item
\texttt{\_assetLinkEntryIds}
\item
\texttt{\_assetPriority}
\item
\texttt{\_createDate}
\item
\texttt{\_formDate}
\item
\texttt{\_indexingEnabled}
\item
\texttt{\_modifiedDate}
\item
\texttt{\_timeZone}
\end{itemize}
\item
URLs, paths and addresses:
\begin{itemize}
\tightlist
\item
\texttt{\_currentURL}
\item
\texttt{\_layoutFullURL}
\item
\texttt{\_layoutURL}
\item
\texttt{\_pathMain}
\item
\texttt{\_pathFriendlyURLPrivateGroup}
\item
\texttt{\_pathFriendlyURLPrivateUser}
\item
\texttt{\_pathFriendlyURLPublic}
\item
\texttt{\_portalURL}
\item
\texttt{\_remoteAddr}
\item
\texttt{\_remoteHost}
\item
\texttt{\_userDisplayURL}
\end{itemize}
\end{itemize}
Are you wondering how the \texttt{ServiceContext} fields get populated?
Good! You'll learn about that next.
\section{Creating and Populating a Service
Context}\label{creating-and-populating-a-service-context}
Although all the \texttt{ServiceContext} class fields are optional,
services that store data with scope must at least specify the scope
group ID. Here's an example of creating a \texttt{ServiceContext}
instance and passing it as a parameter to a Liferay service API:
\begin{verbatim}
ServiceContext serviceContext = new ServiceContext();
serviceContext.setScopeGroupId(myGroupId);
...
_blogsEntryService.addEntry(..., serviceContext);
\end{verbatim}
If you invoke the service from a servlet, a Struts action, or any other
front-end class with access to the \texttt{PortletRequest}, use one of
the \texttt{ServiceContextFactory.getInstance(...)} methods. These
methods create a \texttt{ServiceContext} object from the request and
automatically populate its fields with all the values specified in the
request. The above example looks different if you invoke the service
from a servlet:
\begin{verbatim}
ServiceContext serviceContext =
ServiceContextFactory.getInstance(BlogsEntry.class.getName(), portletRequest);
...
_blogsEntryService.addEntry(..., serviceContext);
\end{verbatim}
You can see an example of populating a \texttt{ServiceContext} with
information from a request object in the code of the
\texttt{ServiceContextFactory.getInstance(...)} methods. The methods
demonstrate how to set parameters like \emph{scope group ID},
\emph{company ID}, \emph{language ID}, and more. They also demonstrate
how to access and populate more complex context parameters like
\emph{tags}, \emph{categories}, \emph{asset links}, \emph{headers}, and
the \emph{attributes} parameter. By calling
\texttt{ServiceContextFactory.getInstance(String\ className,\ PortletRequest\ portletRequest)},
you can assure that your Expando bridge attributes are set on the
\texttt{ServiceContext}. Expandos are the back-end implementation of
custom fields for entities in Liferay.
\section{Creating and Populating a Service Context in
JavaScript}\label{creating-and-populating-a-service-context-in-javascript}
Liferay's API can be invoked in languages other than Java. Some methods
require or allow a \texttt{ServiceContext} parameter. If you're invoking
such a method via Liferay's JSON web services, you might want to create
and populate a \texttt{ServiceContext} object in JavaScript. Creating a
\texttt{ServiceContext} object in JavaScript is no different from
creating any other object in JavaScript.
Before examining a JSON web service invocation that uses a
\texttt{ServiceContext} object, it helps to see a simple JSON web
service example in JavaScript:
\begin{verbatim}
Liferay.Service(
'/user/get-user-by-email-address`,
{
companyId: 20101,
emailAddress: 'test@example.com`
},
function(obj) {
console.log(obj);
}
);
\end{verbatim}
If you run this code, the \emph{test@example.com} user (JSON object) is
logged to the JavaScript console.
The \texttt{Liferay.Service(...)} function takes three arguments:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
A string representing the service being invoked
\item
A parameters object
\item
A callback function
\end{enumerate}
The callback function takes the result of the service invocation as an
argument.
The Liferay JSON web services page (its URL is
\url{localhost:8080/api/jsonws} if you're running Liferay locally on
port 8080) generates example code for invoking web services. To see the
generated code for a particular service, click on the name of the
service, enter the required parameters, and click \emph{Invoke}. The
JSON result of your service invocation appears. There are multiple ways
to invoke Liferay's JSON web services: click on \emph{JavaScript
Example} to see how to invoke the web service via JavaScript, click on
\emph{curl Example} to see how to invoke the web service via curl, or
click on \emph{URL example} to see how to invoke the web service via a
URL.
\begin{figure}
\centering
\includegraphics{./images/jsonws-simple-example.png}
\caption{When you invoke a service from Liferay's JSON web services
page, you can view the result of your service invocation as well as
example code for invoking the service via JavaScript, curl, or URL.}
\end{figure}
Next, you'll learn how to access information from a
\texttt{ServiceContext} object.
\section{Accessing Service Context
Data}\label{accessing-service-context-data}
In this section, you'll find code snippets from
\texttt{BlogsEntryLocalServiceImpl.addEntry(...,\ ServiceContext)}. This
code demonstrates how to access information from a
\texttt{ServiceContext} and provides an example of how the context
information can be used.
As mentioned above, services for entities with scope must get a scope
group ID from the \texttt{ServiceContext} object. This is true for the
Blogs entry service because the scope group ID provides the scope of the
Blogs entry (the entity being persisted). For the Blogs entry, the scope
group ID is used in the following way:
\begin{itemize}
\tightlist
\item
It's used as the \texttt{groupId} for the \texttt{BlogsEntry} entity.
\item
It's used to generate a unique URL for the blog entry.
\item
It's used to set the scope for comments on the blog entry.
\end{itemize}
Here are the corresponding code snippets:
\begin{verbatim}
long groupId = serviceContext.getScopeGroupId();
...
entry.setGroupId(groupId);
...
entry.setUrlTitle(getUniqueUrlTitle(entryId, groupId, title));
...
// Message boards
if (PropsValues.BLOGS_ENTRY_COMMENTS_ENABLED) {
mbMessageLocalService.addDiscussionMessage(
userId, entry.getUserName(), groupId,
BlogsEntry.class.getName(), entryId,
WorkflowConstants.ACTION_PUBLISH);
}
\end{verbatim}
Can \texttt{ServiceContext} be used to access the UUID of the blog
entry? Absolutely! Can you use \texttt{ServiceContext} to set the time
the blog entry was added? You sure can. See here:
\begin{verbatim}
entry.setUuid(serviceContext.getUuid());
...
entry.setCreateDate(serviceContext.getCreateDate(now));
\end{verbatim}
Can \texttt{ServiceContext} be used in setting permissions on resources?
You bet! When adding a blog entry, you can add new permissions or apply
existing permissions for the entry, like this:
\begin{verbatim}
// Resources
if (serviceContext.isAddGroupPermissions() ||
serviceContext.isAddGuestPermissions()) {
addEntryResources(
entry, serviceContext.isAddGroupPermissions(),
serviceContext.isAddGuestPermissions());
}
else {
addEntryResources(
entry, serviceContext.getGroupPermissions(),
serviceContext.getGuestPermissions());
}
\end{verbatim}
\texttt{ServiceContext} helps apply categories, tag names, and the link
entry IDs to asset entries too. Asset links are the back-end term for
related assets in Liferay.
\begin{verbatim}
// Asset
updateAsset(
userId, entry, serviceContext.getAssetCategoryIds(),
serviceContext.getAssetTagNames(),
serviceContext.getAssetLinkEntryIds());
\end{verbatim}
Does \texttt{ServiceContext} also play a role in starting a workflow
instance for the blogs entry? Must you ask?
\begin{verbatim}
// Workflow
if ((trackbacks != null) && (trackbacks.length > 0)) {
serviceContext.setAttribute("trackbacks", trackbacks);
}
else {
serviceContext.setAttribute("trackbacks", null);
}
_workflowHandlerRegistry.startWorkflowInstance(
user.getCompanyId(), groupId, userId, BlogsEntry.class.getName(),
entry.getEntryId(), entry, serviceContext);
\end{verbatim}
The snippet above also demonstrates the \texttt{trackbacks} attribute, a
standard attribute for the blogs entry service. There may be cases where
you need to pass in custom attributes to your blogs entry service. Use
Expando attributes to carry custom attributes along in your
\texttt{ServiceContext}. Expando attributes are set on the added blogs
entry like this:
\begin{verbatim}
entry.setExpandoBridgeAttributes(serviceContext);
\end{verbatim}
You can see that the \texttt{ServiceContext} can be used to transfer
lots of useful information for your services. Understanding how
\texttt{ServiceContext} is used in Liferay helps you determine when and
how to use \texttt{ServiceContext} in your own Liferay application
development.
\section{Related Topics}\label{related-topics-119}
\href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Business
Logic with Service Builder}
\href{/docs/7-2/appdev/-/knowledge_base/a/invoking-local-services}{Invoking
Local Services}
\href{/docs/7-2/frameworks/-/knowledge_base/f/web-services}{Web
Services}
\chapter{Injecting Service Components into Integration
Tests}\label{injecting-service-components-into-integration-tests}
Test driven development plays a key role in quality assurance. Liferay's
tooling and integration with standard test frameworks support test
driven development and help you reach quality milestones. You can use
Liferay DXP's \texttt{@Inject} annotation to inject service components
into an integration test, like you use the
\href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{\texttt{@Reference}
annotation to inject service components} into an OSGi component.
\noindent\hrulefill
\textbf{Note:} \href{http://arquillian.org/}{Arquillian} plus
\href{https://junit.org}{JUnit} annotations is one way to develop
integration tests. Liferay lets you use whatever testing framework you
want.
\noindent\hrulefill
Follow these steps to inject a service component into a test class:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your test class, add a rule field of
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-test-integration/com/liferay/portal/test/rule/LiferayIntegrationTestRule.html}{type
\texttt{com.liferay.portal.test.rule.LiferayIntegrationTestRule}}. For
example,
\begin{verbatim}
@ClassRule
@Rule
public static final AggregateTestRule aggregateTestRule =
new LiferayIntegrationTestRule();
\end{verbatim}
\item
Add a field to hold a service component. Making the field static
improves efficiency because the container injects static fields once
before test runs and nulls them after all tests run. Non-static fields
are injected before each test run but stay in memory till all tests
finish.
\item
Annotate the field with an \texttt{@Inject} annotation. By default,
the container injects the field with a service component object
matching the field's type.
\texttt{@Inject} uses reflection to inject a field with a service
component object matching the field's interface.
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-test-integration/com/liferay/portal/test/rule/LiferayIntegrationTestRule.html}{Test
rule \texttt{LiferayIntegrationTestRule}} provides the annotation.
\item
Optionally add a \texttt{filter} string or \texttt{type} parameter to
further specify the service component object to inject. They can be
used separately or together.
To fill a field with a particular implementation or sub-class object,
set the \texttt{type} with it.
\begin{verbatim}
@Inject(type = SubClass.class)
\end{verbatim}
Replace \texttt{SubClass} with the name of the service interface to
inject.
\end{enumerate}
At runtime, the \texttt{@Inject} annotation blocks the test until a
matching service component is available. The block has a timeout and
messages are logged regarding the test's unavailable dependencies.
\noindent\hrulefill
\textbf{Important}: If you're publishing the service component you are
injecting, the test might never run. If you must publish the service
component from the test class, use
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{Service
Trackers} to access service components.
\noindent\hrulefill
Here's an example test class that injects a \texttt{DDLServiceUpgrade}
object into an \texttt{UpgradeStepRegistrator} interface field:
\begin{verbatim}
public class Test {
@ClassRule
@Rule
public static final AggregateTestRule aggregateTestRule =
new LiferayIntegrationTestRule();
@Test
public void testSomething() {
// your test code here
}
@Inject(
filter = "(&(objectClass=com.liferay.dynamic.data.lists.internal.upgrade.DDLServiceUpgrade))"
)
private static UpgradeStepRegistrator _upgradeStepRegistrator;
}
\end{verbatim}
Great! Now you can inject service components into your tests.
\section{Related Topics}\label{related-topics-120}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{Service
Trackers}
\end{itemize}
\chapter{Upgrade Processes}\label{upgrade-processes}
The development process doesn't end when you first release your
application. Through your own planning, feature requests, and bug
reports, developers improve their applications on a regular basis.
Sometimes, those changes result in changes to the data structure and
underlying database. When users upgrade, they need a process that
transitions them to improved versions of your application. For this, you
must create an upgrade process.
Here's what's involved in creating an upgrade process for your app:
\begin{itemize}
\tightlist
\item
Specifying the schema version
\item
Declaring dependencies
\item
Writing upgrade steps
\item
Writing the registrator
\item
Waiting for upgrade completion
\end{itemize}
Liferay has an Upgrade framework you can use to make this easier to do.
It's a feature-rich framework that makes upgrades safe: the system
records the current state of the schema so that if the upgrade fails,
the process can revert the module back to its previous version.
\href{/docs/7-2/reference/-/knowledge_base/r/meaningful-schema-versioning}{Meaningful
schema versioning} is important to clearly communicate the updates to
your users.
Liferay DXP's Upgrade framework executes your module's upgrades
automatically when the new version starts for the first time. You
implement concrete data schema changes in upgrade step classes and then
register them with the upgrade framework using an \emph{upgrade step}
registrator. An upgrade step is a class that adapts module data to the
module's target database schema. It can execute SQL commands and DDL
files to upgrade the data. The Upgrade framework lets you encapsulate
upgrade logic in multiple upgrade step classes per schema version.
The Upgrade framework executes the upgrade steps to update the current
module data to the latest schema. The registrator's \texttt{register}
method informs the Upgrade framework about each new schema and
associated upgrade steps to adapt data to it. Each schema upgrade is
represented by a \emph{registration}. A registration is an abstraction
for all the changes you need to apply to the database from one schema
version to the next one.
Upgrade registrations are defined by the following values:
\begin{itemize}
\tightlist
\item
\textbf{Module's bundle symbolic name}
\item
\textbf{Schema version to upgrade from} (as a \texttt{String})
\item
\textbf{Schema version to upgrade to} (as a \texttt{String})
\item
\textbf{List of upgrade steps}
\end{itemize}
A registration's upgrade step list can consist of as many upgrade steps
as needed. How you name and organize upgrade steps is up to you.
Liferay's upgrade classes are organized using a package structure
similar to this one:
\begin{itemize}
\tightlist
\item
\emph{some.package.structure}
\begin{itemize}
\tightlist
\item
\texttt{upgrade}
\begin{itemize}
\tightlist
\item
\texttt{v1\_1\_0}
\begin{itemize}
\tightlist
\item
\texttt{UpgradeFoo.java} ← Upgrade Step
\end{itemize}
\item
\texttt{v2\_0\_0}
\begin{itemize}
\tightlist
\item
\texttt{UpgradeFoo.java} ← Upgrade Step
\item
\texttt{UpgradeBar.java} ← Upgrade Step
\end{itemize}
\item
\texttt{MyCustomModuleUpgrade.java} ← Registrator
\end{itemize}
\end{itemize}
\end{itemize}
The example upgrade structure shown above is for a module that has two
database schema versions: \texttt{1.1.0} and \texttt{2.0.0}. They're
represented by packages \texttt{v1\_1\_0} and \texttt{v2\_0\_0}. Each
version package contains upgrade step classes that update the database.
The example upgrade steps focus on fictitious data elements \texttt{Foo}
and \texttt{Bar}. The registrator class (\texttt{MyCustomModuleUpgrade},
in this example) is responsible for registering the applicable upgrade
steps for each schema version.
Here are some organizational tips:
\begin{itemize}
\item
Put all upgrade classes in a sub-package called \texttt{upgrade}.
\item
Group together similar database updates (ones that operate on a data
element or related data elements) in the same upgrade step class.
\item
Create upgrade steps in sub-packages named after each data schema
version.
\end{itemize}
The diagram below illustrates the relationship between the registrator
and the upgrade steps.
\begin{figure}
\centering
\includegraphics{./images/data-upgrade-module-upgrade-architecture.png}
\caption{In a registrator class, the developer specifies a registration
for each schema version upgrade. The upgrade steps handle the database
updates.}
\end{figure}
This section covers these topics:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-upgrade-process-for-your-app}{Creating
an upgrade process for your app}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes-for-former-service-builder-plugins}{Creating
upgrade processes for former service builder plugins}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/upgrading-data-schemas-in-development}{Upgrading
data schemas in development}
\end{itemize}
\chapter{Creating Upgrade Processes for
Modules}\label{creating-upgrade-processes-for-modules}
Follow these steps to create an upgrade process for your module:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your module's \texttt{bnd.bnd} file, and specify a
\texttt{Liferay-Require-SchemaVersion} header with the new schema
version value. Here's an example schema version header for a module
whose new schema is version \texttt{1.1}:
\begin{verbatim}
Liferay-Require-SchemaVersion: 1.1
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Important**: If no `Liferay-Require-SchemaVersion` header is specified,
Liferay DXP considers the `Bundle-Version` header value to be the database
schema version.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Add
a dependency} on the
\href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.portal.upgrade/}{\texttt{com.liferay.portal.upgrade}
module}, along with any other modules your upgrade process requires,
in your your module's dependency management file (e.g., Maven POM,
Gradle build file, or Ivy \texttt{ivy.xml} file). An example
configuration for a \texttt{build.gradle} file is shown below:
\begin{verbatim}
compile group: "com.liferay", name: "com.liferay.portal.upgrade.api", version: "2.0.3"
\end{verbatim}
\item
Create an \texttt{UpgradeProcess} class that extends the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upgrade/UpgradeProcess.html}{\texttt{UpgradeProcess}
base class} ( which implements the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upgrade/UpgradeStep.html}{\texttt{UpgradeStep}
interface}):
\begin{verbatim}
public class MyUpgradeSchemaClass extends UpgradeProcess {
...
}
\end{verbatim}
\item
Override the \texttt{UpgradeProcess} class's \texttt{doUpgrade()}
method with instructions for modifying the database. Use the
\texttt{runSQL} and \texttt{runSQLTemplate*} methods (inherited from
the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/db/BaseDBProcess.html}{\texttt{BaseDBProcess}
class}) to execute your SQL commands and SQL DDL, respectively. If you
want to create, modify, or drop tables or indexes by executing DDL
sentences from an SQL file, make sure to use ANSI SQL only. Doing this
assures the commands work on different databases. If you need to use
non-ANSI SQL, it's best to write it in the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upgrade/UpgradeProcess.html}{\texttt{UpgradeProcess}
class's} \texttt{runSQL} or \texttt{alter} methods, along with tokens
that allow porting the sentences to different databases, as shown in
journal-service module's
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/journal/journal-service/src/main/java/com/liferay/journal/internal/upgrade/v0_0_4/UpgradeSchema.java}{\texttt{UpgradeSchema}
upgrade step class} below which uses the \texttt{runSQLTemplateString}
method to execute ANSI SQL DDL from an SQL file and the \texttt{alter}
method and
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upgrade/UpgradeProcess.html}{\texttt{UpgradeProcess}'s}
\texttt{UpgradeProcess.AlterColumnName} and
\texttt{UpgradeProcess.AlterColumnType} inner classes as token classes
to modify column names and column types:
\begin{verbatim}
public class UpgradeSchema extends UpgradeProcess {
@Override
protected void doUpgrade() throws Exception {
String template = StringUtil.read(
UpgradeSchema.class.getResourceAsStream("dependencies/update.sql"));
runSQLTemplateString(template, false, false);
upgrade(UpgradeMVCCVersion.class);
alter(
JournalArticleTable.class,
new AlterColumnName(
"structureId", "DDMStructureKey VARCHAR(75) null"),
new AlterColumnName(
"templateId", "DDMTemplateKey VARCHAR(75) null"),
new AlterColumnType("description", "TEXT null"));
alter(
JournalFeedTable.class,
new AlterColumnName("structureId", "DDMStructureKey TEXT null"),
new AlterColumnName("templateId", "DDMTemplateKey TEXT null"),
new AlterColumnName(
"rendererTemplateId", "DDMRendererTemplateKey TEXT null"),
new AlterColumnType("targetPortletId", "VARCHAR(200) null"));
}
}
\end{verbatim}
Here's a simpler example upgrade step from the
\texttt{com.liferay.calendar.service} module. It uses the
\texttt{alter} method to modify a column type in the calendar booking
table:
\begin{verbatim}
public class UpgradeCalendarBooking extends UpgradeProcess {
@Override
protected void doUpgrade() throws Exception {
alter(
CalendarBookingTable.class,
new AlterColumnType("description", "TEXT null"));
}
}
\end{verbatim}
\item
If your application was modularized from a former traditional Liferay
plugin application (application WAR) and it uses Service Builder,
\href{/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes-for-former-service-builder-plugins}{create
and register a Bundle Activator} to register it in Liferay DXP's
\texttt{Release\_} table.
\item
Create an \texttt{UpgradeStepRegistrator} OSGi Component class of
service type \texttt{UpgradeStepRegistrator.class} that implements the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/portal/upgrade/registry/UpgradeStepRegistrator.html}{\texttt{UpgradeStepRegistrator}
interface}:
\begin{verbatim}
package com.liferay.mycustommodule.upgrade;
import com.liferay.portal.upgrade.registry.UpgradeStepRegistrator;
import org.osgi.service.component.annotations.Component;
@Component(immediate = true, service = UpgradeStepRegistrator.class)
public class MyCustomModuleUpgrade implements UpgradeStepRegistrator {
}
\end{verbatim}
\item
Override the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/portal/upgrade/registry/UpgradeStepRegistrator.html\#register-com.liferay.portal.upgrade.registry.UpgradeStepRegistrator.Registry-}{\texttt{register}
method} to implement the module's upgrade registrations---abstractions
for the upgrade steps required to update the database from one schema
version to the next. For example, the upgrade step registrator class
\texttt{MyCustomModuleUpgrade} (below) registers three upgrade steps
incrementally for each schema version (past and present,
\texttt{0.0.0} to \texttt{2.0.0}, \texttt{1.0.0} to \texttt{1.1.0},
and \texttt{1.1.0} to \texttt{2.0.0}).
The first registration is applied if the module hasn't been installed
previously. It contains only one empty upgrade step:
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-kernel/src/com/liferay/portal/kernel/upgrade/DummyUpgradeStep.java}{new
\texttt{DummyUpgradeStep}}(). This registration records the module's
latest schema version (i.e., \texttt{2.0.0}) in Liferay DXP's
\texttt{Release\_} table. Note that if the same class name is used in
multiple packages, you must provide the fully qualified class name for
the class, as shown in the second registration (\texttt{1.0.0} to
\texttt{1.1.0}) below for the \texttt{UpgradeFoo} class:
\begin{verbatim}
package com.liferay.mycustommodule.upgrade;
import com.liferay.portal.upgrade.registry.UpgradeStepRegistrator;
import org.osgi.service.component.annotations.Component;
@Component(immediate = true, service = UpgradeStepRegistrator.class)
public class MyCustomModuleUpgrade implements UpgradeStepRegistrator {
@Override
public void register(Registry registry) {
registry.register(
"com.liferay.mycustommodule", "0.0.0", "2.0.0",
new DummyUpgradeStep());
registry.register(
"com.liferay.mycustommodule", "1.0.0", "1.1.0",
new com.liferay.mycustommodule.upgrade.v1_1_0.UpgradeFoo());
registry.register(
"com.liferay.mycustommodule", "1.1.0", "2.0.0",
new com.liferay.mycustommodule.upgrade.v2_0_0.UpgradeFoo(),
new UpgradeBar());
}
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Important**: Modules that use Service Builder *should not* define a
registration for their initial database schema version, as Service Builder
already records their schema versions to Liferay DXP's `Release_` table. Modules
that don't use Service Builder, however, *should* define a registration for
their initial schema.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\item
If your upgrade step uses an OSGi service, \textbf{your upgrade must
wait for that service's availability}. Use the \texttt{@Reference}
annotation to declare any classes that the registrator class depends
on. For example, the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/wiki/wiki-service/src/main/java/com/liferay/wiki/internal/upgrade/WikiServiceUpgrade.java}{\texttt{WikiServiceUpgrade}
registrator class} below references the \texttt{SettingsFactory} class
for the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/wiki/wiki-service/src/main/java/com/liferay/wiki/internal/upgrade/v1_0_0/UpgradePortletSettings.java}{\texttt{UpgradePortletSettings}
upgrade step}. The \texttt{setSettingsFactory} method's
\texttt{@Reference} annotation declares that the registrator class
depends on and must wait for the \texttt{SettingsFactory} service to
be available in the run time environment:
\begin{verbatim}
@Component(immediate = true, service = UpgradeStepRegistrator.class)
public class WikiServiceUpgrade implements UpgradeStepRegistrator {
@Override
public void register(Registry registry) {
registry.register("0.0.1", "0.0.2", new UpgradeSchema());
registry.register("0.0.2", "0.0.3", new UpgradeKernelPackage());
registry.register(
"0.0.3", "1.0.0", new UpgradeCompanyId(),
new UpgradeLastPublishDate(), new UpgradePortletPreferences(),
new UpgradePortletSettings(_settingsFactory), new UpgradeWikiPage(),
new UpgradeWikiPageResource());
registry.register("1.0.0", "1.1.0", new UpgradeWikiNode());
registry.register(
"1.1.0", "1.1.1",
new UpgradeDiscussionSubscriptionClassName(
_subscriptionLocalService, WikiPage.class.getName(),
UpgradeDiscussionSubscriptionClassName.DeletionMode.ADD_NEW));
registry.register(
"1.1.1", "2.0.0",
new BaseUpgradeSQLServerDatetime(
new Class>[] {WikiNodeTable.class, WikiPageTable.class}));
}
@Reference
private SettingsFactory _settingsFactory;
@Reference
private SubscriptionLocalService _subscriptionLocalService;
}
\end{verbatim}
\item
Upgrade the database to the latest database schema version before
making its services available. To do this, configure the Bnd header
\texttt{Liferay-Require-SchemaVersion} to the latest schema version
for \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} services. For all other services, specify an
\texttt{@Reference} annotation that targets the containing module and
its latest schema version.
Here are the target's required attributes:
\begin{itemize}
\tightlist
\item
\texttt{release.bundle.symbolic.name}: module's bundle symbolic name
\item
\texttt{release.schema.version}: module's current schema version
\end{itemize}
For example, the \texttt{com.liferay.comment.page.comments.web}
module's
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/comment/comment-page-comments-web/src/main/java/com/liferay/comment/page/comments/web/internal/portlet/PageCommentsPortlet.java}{\texttt{PageCommentsPortlet}
class} upgrades to schema version \texttt{2.0.0} by defining the
reference below:
\begin{verbatim}
@Reference(
target = "(&(release.bundle.symbolic.name=com.liferay.comment.page.comments.web)(release.schema.version=2.0.0))"
)
private Release _release;
\end{verbatim}
Dependencies between OSGi services can reduce the number of service
classes in which upgrade reference annotations are needed. For
example, there's no need to add an upgrade reference in a dependent
service, if the dependency already refers to the upgrade.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note**: Data verifications using the class `VerifyProcess` are deprecated.
Verifications should be tied to schema versions. Upgrade processes are associated
with schema versions but `VerifyProcess` instances are not.
\end{verbatim}
\noindent\hrulefill
Great! Now you know how to create data upgrades for all your modules.
\section{Related Topics}\label{related-topics-121}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes-for-former-service-builder-plugins}{Upgrade
Processes for Former Service Builder Plugins}
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{Upgrading
Code to 7.0}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/configurable-applications}{Configurable
Applications}
\end{itemize}
\chapter{Upgrade Processes for Former Service Builder
Plugins}\label{upgrade-processes-for-former-service-builder-plugins}
If you modularized a traditional Liferay plugin application that
implements Service Builder services, your new modular application must
register itself in the Liferay DXP's \texttt{Release\_} table. This is
required regardless of whether release records already exist for
previous versions of the app. A Bundle Activator is the recommended way
to add a release record for the first modular version of your converted
application.
\textbf{Important}: The steps covered in this article only apply to
modular applications that use Service Builder and were modularized from
traditional Liferay plugin applications. They do not apply to you if
your application doesn't use Service Builder or has never been a
traditional Liferay plugin application (a WAR application).
Bundle Activator class code is dense but straightforward. Referring to
an example Bundle Activator can be helpful. Here's the Liferay Knowledge
Base application's Bundle Activator:
\begin{verbatim}
public class KnowledgeBaseServiceBundleActivator implements BundleActivator {
@Override
public void start(BundleContext bundleContext) throws Exception {
Filter filter = bundleContext.createFilter(
StringBundler.concat(
"(&(objectClass=", ModuleServiceLifecycle.class.getName(), ")",
ModuleServiceLifecycle.DATABASE_INITIALIZED, ")"));
_serviceTracker = new ServiceTracker(
bundleContext, filter, null) {
@Override
public Object addingService(
ServiceReference serviceReference) {
try {
BaseUpgradeServiceModuleRelease
upgradeServiceModuleRelease =
new BaseUpgradeServiceModuleRelease() {
@Override
protected String getNamespace() {
return "KB";
}
@Override
protected String getNewBundleSymbolicName() {
return "com.liferay.knowledge.base.service";
}
@Override
protected String getOldBundleSymbolicName() {
return "knowledge-base-portlet";
}
};
upgradeServiceModuleRelease.upgrade();
return null;
}
catch (UpgradeException ue) {
throw new RuntimeException(ue);
}
}
};
_serviceTracker.open();
}
@Override
public void stop(BundleContext bundleContext) {
_serviceTracker.close();
}
private ServiceTracker _serviceTracker;
}
\end{verbatim}
Follow these steps to create a Bundle Activator:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a class that implements the
\texttt{org.osgi.framework.BundleActivator} interface:
\begin{verbatim}
public class KnowledgeBaseServiceBundleActivator implements BundleActivator {
}
\end{verbatim}
\item
Add a service tracker field:
\begin{verbatim}
`private ServiceTracker _serviceTracker;`
\end{verbatim}
\item
Override BundleActivator's \texttt{stop} method to close the service
tracker:
\begin{verbatim}
@Override
public void stop(BundleContext bundleContext) throws Exception {
_serviceTracker.close();
}
\end{verbatim}
\item
Override BundleActivator's \texttt{start} method to instantiate a
service tracker that creates a filter to listen for the app's database
initialization event and initializes the service tracker to use that
filter. You'll add the service tracker initialization code in the next
steps. At the end of the \texttt{start} method, open the service
tracker.
\begin{verbatim}
@Override
public void start(BundleContext bundleContext) throws Exception {
Filter filter = bundleContext.createFilter(
StringBundler.concat(
"(&(objectClass=", ModuleServiceLifecycle.class.getName(), ")",
ModuleServiceLifecycle.DATABASE_INITIALIZED, ")"));
_serviceTracker = new ServiceTracker(
bundleContext, filter, null) {
// See the next step for this code ...
};
_serviceTracker.open();
}
\end{verbatim}
\item
In the service tracker initialization block
\texttt{\{\ //\ See\ the\ next\ step\ for\ this\ \ \ \ \ \ code\ ...\ \}}
from the previous step, add an \texttt{addingService} method that
instantiates a \texttt{BaseUpgradeServiceModuleRelease} for describing
your app. The example \texttt{BaseUpgradeServiceModuleRelease}
instance below describes Liferay's Knowledge Base app:
\begin{verbatim}
@Override
public Object addingService(
ServiceReference serviceReference) {
try {
BaseUpgradeServiceModuleRelease
upgradeServiceModuleRelease =
new BaseUpgradeServiceModuleRelease() {
@Override
protected String getNamespace() {
return "KB";
}
@Override
protected String getNewBundleSymbolicName() {
return "com.liferay.knowledge.base.service";
}
@Override
protected String getOldBundleSymbolicName() {
return "knowledge-base-portlet";
}
};
upgradeServiceModuleRelease.upgrade();
return null;
}
catch (UpgradeException ue) {
throw new RuntimeException(ue);
}
}
\end{verbatim}
The \texttt{BaseUpgradeServiceModuleRelease} implements the following
methods:
\begin{itemize}
\tightlist
\item
\texttt{getNamespace}: Returns the namespace value as specified in
the former plugin's \texttt{service.xml} file. This value is also in
the \texttt{buildNamespace} field in the plugin's
\texttt{ServiceComponent} table record.
\item
\texttt{getOldBundleSymbolicName}: Returns the former plugin's name.
\item
\texttt{getNewBundleSymbolicName}: Returns the module's symbolic
name. In the module's \texttt{bnd.bnd} file, it's the
\texttt{Bundle-SymbolicName} value.
\item
\texttt{upgrade}: Invokes the app's upgrade processes.
\end{itemize}
\item
In the module's \texttt{bnd.bnd} file, reference the Bundle Activator
class you created. Here's the example's Bundle Activator reference:
\begin{verbatim}
Bundle-Activator: com.liferay.knowledge.base.internal.activator.KnowledgeBaseServiceBundleActivator
\end{verbatim}
\end{enumerate}
The Bundle Activator uses one of the following values to initialize the
\texttt{schemaVersion} field in the application's \texttt{Release\_}
table record:
\begin{itemize}
\tightlist
\item
Current \texttt{buildNumber}: if there is an existing
\texttt{Release\_} table record for the previous plugin.
\item
\texttt{0.0.1}: if there is no existing \texttt{Release\_} table
record.
\end{itemize}
Wonderful! You've set your service module's data upgrade process.
\section{Related Topics}\label{related-topics-122}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-upgrade-process-for-your-app}{Creating
Upgrade Processes for Modules}
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{Upgrading
Code to 7.0}
\end{itemize}
\chapter{Upgrading Data Schemas in
Development}\label{upgrading-data-schemas-in-development}
As you develop modules, you might need to iterate through several
database schema changes. Before you release new module versions with
your finalized schema changes, you must create a formal
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-upgrade-process-for-your-app}{data
upgrade process}. Until then, you can use the Build Auto Upgrade feature
to test schema changes on the fly.
Follow these steps to use the Build Auto Upgrade feature to test schema
changes in development:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a \texttt{portal-ext.properties} file in your app server's
\texttt{{[}Liferay\_Home{]}/} folder if it doesn't already exist.
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Enable
Developer Mode in your app server} by adding the following line to the
properties file:
\begin{verbatim}
include-and-override=portal-developer.properties;
\end{verbatim}
The Build Auto Upgrade feature is a global property
\texttt{schema.module.build.auto.upgrade} in the file
\texttt{{[}Liferay\_Home{]}/portal-developer.properties}, so enabling
Developer Mode automatically enables this property as well.
Alternatively, if you prefer not to enable all the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-impl/src/portal-developer.properties}{other
properties included in Developer Mode}, you can just add the
\texttt{schema.module.build.auto.upgrade} property to your
\texttt{portal-ext.properties} file and set it to \texttt{true}:
\begin{verbatim}
schema.module.build.auto.upgrade = true;
\end{verbatim}
\end{enumerate}
Setting the global property \texttt{schema.module.build.auto.upgrade} to
\texttt{true} applies module schema changes for redeployed modules whose
service build numbers have incremented. The \texttt{build.number}
property in the module's \texttt{service.properties} file indicates the
service build number. Build Auto Upgrade executes schema changes without
massaging existing data. It leaves data empty for created columns, drops
data from deleted and renamed columns, and orphans data from deleted and
renamed tables.
Although Build Auto Upgrade updates databases quickly and automatically,
it doesn't guarantee a proper data upgrade--you implement that via
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-upgrade-process-for-your-app}{data
upgrade processes}. Build Auto Upgrade is for development purposes only.
\noindent\hrulefill
\textbf{WARNING}: DO NOT USE the Build Auto Upgrade feature in
production. Liferay DXP DOES NOT support Build Auto Upgrade in
production. Build Auto Upgrade is for development purposes only.
Enabling it in production can result in data loss and improper data
upgrade. In production environments, leave the property
\texttt{schema.module.build.auto.upgrade} in
\texttt{portal-developer.properties} set to \texttt{false}.
\noindent\hrulefill
By default, \texttt{schema.module.build.auto.upgrade} is set to
\texttt{false}. On any module's first deployment, the module's tables
are generated regardless of the
\texttt{schema.module.build.auto.upgrade} value.
The table below summarizes Build Auto Upgrade's handling of schema
changes:
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5200}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.4800}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Schema Change
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Result
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Add column & Create a new empty column. \\
Rename column & Drop the existing column and delete all its data. Create
a new empty column. \\
Delete column & Drop the existing column and delete all its data. \\
Create or rename a table in Liferay DXP's built-in data source. & Orphan
the existing table and all its data. Create the new table. \\
\end{longtable}
Great! Now you know how to use the Build Auto Upgrade developer feature.
\section{Related Topics}\label{related-topics-123}
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-upgrade-process-for-your-app}{Creating
Data Upgrade Process for Modules}
\chapter{Managing User-Associated Data Stored by Custom
Applications}\label{managing-user-associated-data-stored-by-custom-applications}
Administrators can
\href{/docs/7-2/user/-/knowledge_base/u/managing-user-data}{delete or
anonymize} User Associated Data (UAD) using management tools that aid
compliance efforts with the EU's General Data Protection Regulation
(GDPR). Out of the box, the UAD management tool supports Liferay DXP
applications (Blogs, Documents and Media, etc.), and you can also
anonymize data stored by your custom applications.
This task is made easier for
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder applications}. At the core of the anonymization effort, you must
identify the model entity's fields to anonymize. With Service Builder,
attach anonymization attributes to elements in the \texttt{-service}
module's \texttt{service.xml} file. For the entire DTD for Service
Builder, see
\href{https://docs.liferay.com/portal/7.2-ga1/definitions/}{here}. These
two are the most important attributes for the UAD framework:
\begin{itemize}
\item
The \texttt{uad-anonymize-field-name=fieldName} attribute indicates a
field whose value is replaced by that of the anonymous user in the UAD
deletion process.
\item
The \texttt{uad-nonanonymizable=true} attribute indicates data that
cannot be anonymized automatically and must be reviewed by an
administrator.
\end{itemize}
Once your application uses the UAD framework to manage User data, there
are more features in 7.0 that make searching and deleting User
Associated Data even easier.
\chapter{Adding the UAD Framework to a Service Builder
Application}\label{adding-the-uad-framework-to-a-service-builder-application}
You'll touch two modules of a Service Builder application to implement
the UAD features: the \texttt{-service} module and a brand new module
that Service Builder generates for you, the \texttt{-uad} module.
\section{Update the Service Module}\label{update-the-service-module}
Before you specify your model entity's fields to manage with the UAD
framework, make sure you have the right dependencies.
\section{Include Dependencies}\label{include-dependencies}
To compile the code that Service Builder generates, you need
dependencies on \texttt{com.liferay.petra.String} and
\texttt{com.liferay.portal\ kernel}. Make sure your service module's
\texttt{build.gradle} includes both:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "4.4.0"
compileOnly group: "com.liferay", name: "com.liferay.petra.string", version: "3.0.0"
...
}
\end{verbatim}
\section{Choose the Fields to
Anonymize}\label{choose-the-fields-to-anonymize}
In the \texttt{service.xml} file, choose the fields to be handled by the
framework by adding a
\texttt{uad-anonymize-field-name="{[}fieldName{]}"} or
\texttt{uad-nonanonymizable=true}.
For example, to replace the \texttt{userName} field of your custom
entity with the \texttt{fullName} of the anonymous user,
\begin{verbatim}
\end{verbatim}
This declaration specifies that the \texttt{content} field cannot be
auto-anonymized by the framework but should be reviewed manually.
\begin{verbatim}
\end{verbatim}
Run Service Builder. A new module is generated alongside your other
modules, called \texttt{my-app-uad}. It requires a little massaging.
\section{Update the UAD Module}\label{update-the-uad-module}
First, include your dependencies, and then provide your application's
name to the user interface.
\section{Include Dependencies}\label{include-dependencies-1}
The new module is generated without a build script, so you must provide
one. It should include dependencies on
\texttt{osgi.service.component.annotations},
\texttt{com.liferay.portal.kernel}, \texttt{com.liferay.petra.string},
the \texttt{com.liferay.user.associated.data.api}, and your own
application's \texttt{-api} module:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "4.4.0"
compileOnly group: "com.liferay", name: "com.liferay.user.associated.data.api", version: "4.1.1"
compileOnly group: "com.liferay", name: "com.liferay.petra.string", version: "3.0.0"
compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
compileOnly project(":modules:custom:custom-api")
}
\end{verbatim}
\section{Provide Your App's Name to the
UI}\label{provide-your-apps-name-to-the-ui}
The simplest way to provide your app's name to the anonymization UI is
to include a language key in your \texttt{Language.properties} file:
\begin{verbatim}
application.name.[Bundle-SymbolicName]=Custom App
\end{verbatim}
The bracketed text is the \texttt{Bundle-SymbolicName} from your
\texttt{-uad} module's \texttt{bnd.bnd} file.
While this approach is recommended, it has one downside: multiple
language keys are used to label a single application. Liferay DXP
applications use the \texttt{com.liferay.lang.merger} plugin to avoid
this. Here's the relevant part of the Blogs application's
\texttt{blogs-uad/build.gradle}:
\begin{verbatim}
apply plugin: "com.liferay.lang.merger"
dependencies {
...
}
mergeLang {
setting("../blogs-web/src/main/resources/content") {
transformKey "javax.portlet.title.com_liferay_blogs_web_portlet_BlogsPortlet", "application.name.com.liferay.blogs.uad"
}
sourceDirs = ["../blogs-web/src/main/resources/content"]
}
\end{verbatim}
\chapter{Enhancing the Data Erasure
UI}\label{enhancing-the-data-erasure-ui}
As of 7.0, there are new features that enhance an administrator's
experience finding data in the Personal Data Erasure UI:
\begin{description}
\tightlist
\item[\textbf{Filtering}]
User content can now be viewed and acted upon based on whether it is
part of the User's personal Site, some other Site, or the overall
company.
\item[\textbf{Search}]
A search bar now allows for filtering content based on a search term.
\item[\textbf{Hierarchy Display}]
If there are multiple types of content that are related in a hierarchy,
you can define that relationship using a
\texttt{UADHierarchyDeclaration}, and the user interface shows that
hierarchy (e.g.~files and folders).
\end{description}
\section{Filtering and Searching in the Data Erasure
UI}\label{filtering-and-searching-in-the-data-erasure-ui}
To support filtering and searching in your custom entities, implement
three methods in the \texttt{UADDisplay} class (found in your
\texttt{-uad} module):
\begin{itemize}
\tightlist
\item
\texttt{isSiteScoped}
\item
\texttt{search}
\item
\texttt{searchCount}
\end{itemize}
The \texttt{isSiteScoped} method returns a boolean, determining if the
entity can be associated with a particular Site. This is used to
determine which filter they are associated with (``instance'',
``personal-site'', or ``regular-sites'').
\begin{figure}
\centering
\includegraphics{./images/uad-scope-filter.png}
\caption{Items in the Personal Data Erasure screen can be filtered by
scope.}
\end{figure}
The \texttt{search} method takes the following parameters:
\texttt{userId}: The \texttt{userId} of the selected User.
\texttt{groupIds}: An array of \texttt{groupId}s used to filter which
data is shown by which groups it is associated with. In the case that no
\texttt{groupId}s are given (it can be null), the search method should
return data that is not scoped to any given group.
\texttt{keywords}: The contents of the search bar. The search method
should filter by whatever fields are relevant for the given entity.
\texttt{orderByField}: The name of the field used to sort the results.
This is one of the names returned by \texttt{getSortingFieldNames}.
\texttt{orderByType}: Sort the results in ascending order or descending
order (\texttt{asc} or \texttt{desc}), for pagination.
\texttt{start}: The starting index of the result set. For pagination.
\texttt{end}: The ending index of the result set. For pagination.
The \texttt{searchCount} method takes the following parameters, which
are treated identically to the ones in \texttt{search}:
\begin{itemize}
\item
\texttt{userId}
\item
\texttt{groupIds}
\item
\texttt{keywords}
\end{itemize}
Read
\href{/docs/7-2/frameworks/-/knowledge_base/f/filtering-and-searching-uad-marked-entities}{here}
for instructions on how to implement search and filtering for your
entity.
\section{Hierarchy Display}\label{hierarchy-display}
Hierarchical UAD display optionally shows entities with a natural
parent-child relationship (for example, Document Library Folders and
Document Library File Entries). Viewing these entities in a hierarchy
helps administrators make sense of the data they're reviewing for
possible erasure.
\begin{figure}
\centering
\includegraphics{./images/uad-hierarchy.png}
\caption{Hierarchical representation of nested entities is useful for
administrators reviewing User data for possible deletion.}
\end{figure}
To implement a hierarchy display, you must do two things:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Implement a
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/user-associated-data/user-associated-data-api/src/main/java/com/liferay/user/associated/data/display/UADHierarchyDeclaration.java}{\texttt{UADHierarchyDeclaration}}
class.
\item
Add a method to the \texttt{*UADDisplay} class for each type involved
in the hierarchy.
\end{enumerate}
Once implemented, a hierarchy view is displayed for any of the types
returned in the \texttt{UADHierarchyDeclaration}. For container
entities, a count of all child entities is calculated and displayed
using the hierarchy-related methods in \texttt{UADDisplay}.
\section{UAD Hierarchy Declaration}\label{uad-hierarchy-declaration}
The \texttt{UADHierarchyDeclaration} defines the types in the
hierarchical relationship. There are two classifications for a type in a
hierarchy: a \emph{container}, and a \emph{non-container}. These are
defined by \texttt{getContainerUADDisplays} and
\texttt{getNoncontainerUADDisplays}.
Each returns an array of one or more \texttt{UADDisplay} classes.
Containers can be parents or children in the hierarchy. An example is a
folder in a file system, which can contain both files and other folders.
The non-container entities can only be children in the hierarchy. An
example is a file in a file system.
The \texttt{UADHierarchyDeclaration} provides some other methods for
display purposes.
\begin{itemize}
\item
A label for the hierarchy is provided through the
\texttt{getEntitiesTypeLabel} method.
\item
If any additional information is to be displayed in the table for any
of the entity types in addition to the count, those column names
should be returned by \texttt{getExtraColumnNames}. This is optional.
\end{itemize}
See
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/document-library/document-library-uad/src/main/java/com/liferay/document/library/uad/display/DLUADHierarchyDeclaration.java}{\texttt{DLUADHierarchyDeclaration}}
for an example.
\section{Add methods to UADDisplay}\label{add-methods-to-uaddisplay}
Each type involved in the hierarchy should implement some additional
methods in \texttt{UADDisplay}.
These methods must be implemented by containers and non-containers
alike, in the \texttt{*UADDisplay} class:
\texttt{getName} returns a display name for the given entity.
\texttt{getParentContainerClass} returns the class of the type that
contains this type. It can return itself (for example, a folder can
contain a folder).
\texttt{getParentContainerId} returns the primary key of the container
that contains the entity passed to this method.
\texttt{isUserOwned} returns whether or not the given entity is owned by
the user.
Additionally, implement \texttt{getTopLevelContainer} in the
\texttt{*UADDisplay} class for all types classified as
\emph{containers}. It's used to derive the count of how many user-owned
entities are contained inside a given container's tree. It answers the
question ``which type \texttt{T} ancestor of \texttt{childObject} is an
immediate child of the container identified by
\texttt{parentContainerClass} and \texttt{parentContainerId}?'' The
method may return \texttt{null} if \texttt{childObject} is not a child
of the parent container. This method is the most complicated to
implement and requires some consideration for each case. Refer to the
test case for examples of the requirements used for \texttt{DLFolder}:
\href{https://github.com/liferay/liferay-portal/blob/c8f78609353d6a83a0b755b0bbf93764959821ee/modules/apps/document-library/document-library-uad-test/src/testIntegration/java/com/liferay/document/library/uad/display/test/DLFolderUADDisplayTest.java\#L67}{DLFolderUADDisplayTest\#testGetTopLevelContainer}
See the actual implementation for \texttt{DLFolder} in
\href{https://github.com/liferay/liferay-portal/blob/c8f78609353d6a83a0b755b0bbf93764959821ee/modules/apps/document-library/document-library-uad/src/main/java/com/liferay/document/library/uad/display/DLFolderUADDisplay.java\#L105}{DLFolderUADDisplay\#getTopLevelContainer}.
The method returns either \texttt{null} or the container object of type
T that is the top level container of the \texttt{childObject} (which
could be any type of object that is a part of the hierarchy). This
container does not necessarily have to be owned by the user, but is
understood to contain data related to the user. This information is used
to count how much user data is inside the container designated by
\texttt{parentContainerClass} and \texttt{parentContainerId}.
\begin{verbatim}
@Override
public DLFolder getTopLevelContainer(
Class> parentContainerClass, Serializable parentContainerId,
Object childObject) {
try {
DLFolder childFolder = null;
if (childObject instanceof DLFileEntry) {
DLFileEntry dlFileEntry = (DLFileEntry)childObject;
childFolder = dlFileEntry.getFolder();
}
else {
childFolder = (DLFolder)childObject;
}
long parentFolderId = (long)parentContainerId;
if ((childFolder.getFolderId() == parentFolderId) ||
((parentFolderId !=
DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) &&
!StringUtil.contains(
childFolder.getTreePath(), String.valueOf(parentFolderId),
"/"))) {
return null;
}
if (childFolder.getParentFolderId() == parentFolderId) {
return childFolder;
}
List ancestorFolderIds = childFolder.getAncestorFolderIds();
if (parentFolderId == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
return get(ancestorFolderIds.get(ancestorFolderIds.size() - 1));
}
if (ancestorFolderIds.contains(parentFolderId)) {
return get(
ancestorFolderIds.get(
ancestorFolderIds.indexOf(parentFolderId) - 1));
}
}
catch (PortalException pe) {
_log.error(pe, pe);
}
return null;
}
\end{verbatim}
The exact implementation details vary for each entity type.
\chapter{Filtering and Searching UAD-Marked
Entities}\label{filtering-and-searching-uad-marked-entities}
In the data erasure UI, it's important that administrators can find what
they're looking for. The native Liferay DXP entities support filtering
and search, and when you follow the steps here, your entities will, too.
To add filtering and searching for your custom entities, implement three
methods in the \texttt{UADDisplay} class (in your application's
\texttt{-uad} module):
\section{Filtering}\label{filtering}
The \texttt{isSiteScoped} method returns a boolean denoting if the
entities can be associated with a particular Site: \texttt{false} if
not, and \texttt{true} if the entities are scoped to a Site. This
determines which filter they are associated with (``instance'',
``personal-site'', or ``regular-sites'').
\begin{verbatim}
@Override
public boolean isSiteScoped() {
return false;
}
\end{verbatim}
\section{Search}\label{search-1}
Implement the \texttt{search} and \texttt{searchCount} methods to enable
search in the UAD interface:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The \texttt{search} method must return a \texttt{List} of entities
associated with the \texttt{userId}. For example, you could search the
database for records associated with the \texttt{userId}:
\begin{verbatim}
@Override
public List search(
long userId, long[] groupIds, String keywords, String orderByField,
String orderByType, int start, int end) {
FooService fooService = getFooService();
return dummyService.getEntities(userId);
}
\end{verbatim}
But if you've gone through the trouble of indexing your model entity's
fields in a search engine, it's more likely you'll want to do the
initial search, querying for documents matching the \texttt{userId},
at the search engine level. After the search, retrieve the matching
entities from the database.
\begin{verbatim}
@Override
public List search(
long userId, long[] groupIds, String keywords, String orderByField,
String orderByType, int start, int end) {
SearchContext searchContext = new SearchContext();
searchContext.setStart(start);
searchContext.setEnd(end);
searchContext.setGroupIds(groupIds);
searchContext.setKeywords(keywords);
BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(
searchContext);
booleanQuery.addExactTerm("userId", userId);
BooleanClause booleanClause = BooleanClauseFactoryUtil.create(
booleanQuery, BooleanClauseOccur.MUST.getName());
searchContext.setBooleanClauses(new BooleanClause[] {booleanClause});
Indexer indexer = IndexerRegistryUtil.getIndexer(FooEntry.class);
Hits hits = indexer.search(searchContext);
List fooEntries = new ArrayList();
for (int i = 0; i < hits.getDocs().length; i++) {
Document doc = hits.doc(i);
long entryId = GetterUtil
.getLong(doc.get(Field.ENTRY_CLASS_PK));
Entry entry = null;
try {
entry = _fooEntryLocalService.getFooEntry(fooEntryId);
} catch (PortalException pe) {
_log.error(pe.getLocalizedMessage());
} catch (SystemException se) {
_log.error(se.getLocalizedMessage());
}
fooEntries.add(fooEntry);
}
return fooEntries;
}
\end{verbatim}
It largely boils down to instantiating and populating the search
context, which gets passed to the \texttt{indexer.search} call to
retrieve the \texttt{Hits}. Subsequently, populate the \texttt{List}
by iterating through the \texttt{Hits}, using each one's
\texttt{ENTRY\_CLASS\_PK} field as the primary key of the entity in
the call to the entity's getter. The \texttt{BooleanClause}
construction and inclusion in the search context ensures that all the
results returned correspond to the \texttt{userId} that's passed to
this method.
\item
The \texttt{searchCount} method returns a long of the result
\texttt{List}'s \texttt{size} method. You could just invoke the
class's \texttt{search} method, then call the \texttt{List} object's
\texttt{size} method.
\begin{verbatim}
@Override
public long searchCount(long userId, long[] groupIds, String keywords) {
List results = search(
userId, groupIds, keywords, null, null, QueryUtil.ALL_POS,
QueryUtil.ALL_POS);
return results.size();
}
\end{verbatim}
But, again, if the model entity is being indexed in a search engine,
you can use it to get a count without ever hitting the database. Using
the \texttt{Hits} object returned from a search (see the code from
step 1, but don't include \texttt{start} and \texttt{end} parameters
in the \texttt{SearchContext}), call \texttt{hits.getLegnth()} and you
get the count, as an \texttt{int}.
\end{enumerate}
Now administrators responsible for complying with GDPR or other data
erasure concerns can search and filter your entity from the Liferay DXP
UAD interface.
\chapter{Web Experience Management}\label{web-experience-management}
Web Experience Management encompasses Liferay's features and tools for
building Sites and creating content. Many of these, like Web Content
Management and Page Templates, are graphical tools used by
administrators and marketers. Others, like Page Fragments, let web
developers flex their muscles in content creation. These articles cover
where web development intersects with user experience and how to use
Liferay's Web Experience frameworks to integrate custom applications
into Liferay DXP.
Specifically, you'll learn about
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{Developing
Fragments}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/supporting-custom-content-types-in-content-and-display-pages}{Supporting
Custom Content Types}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/screen-navigation-framework}{Screen
Navigation}
\end{itemize}
For more information on applying these frameworks for users, see the
\href{/docs/7-2/user/-/knowledge_base/u/web-experience-management}{Web
Experience Management} user articles.
\chapter{Page Fragments}\label{page-fragments}
You can use Page Fragments to take your design vision and accurately
realize it on a web page. You start with a ``blank slate.'' You then
have three tools at your disposal to accomplish your vision:
\textbf{HTML}: The markup of the fragment. Fragments use standard HTML
with special tags to add dynamic behavior.
\textbf{CSS}: Styles and positions the fragment's markup.
\textbf{JavaScript}: Provides dynamic behavior to the fragment.
The HTML, CSS, and JavaScript are all completely standard, but can be
enhanced with Liferay-specific features. You can specify text, images,
and links as editable and provide for ``rich'' text with formatting.
You can also access the FreeMarker templates engine from your HTML using
the
\href{https://freemarker.apache.org/docs/dgui_misc_alternativesyntax.html}{alternative
(square bracket) syntax}. Learn more about available FreeMarker objects
in
\href{/docs/7-2/reference/-/knowledge_base/r/front-end-reference}{Front-end
Reference}.
Liferay portlets can also be embedded in Fragments as widgets, making
pages with Fragments more dynamic than regular web content.
Now you'll step through some Page Fragment basics.
\section{Developing Page Fragments}\label{developing-page-fragments}
There are two types of Page Fragments: \emph{Sections} and
\emph{Components}. A Section defines columns, padding, and spacing on
the page. A Component contains content that is added to a Section.
Fragments are created inside of Collections. Collections provide an easy
way to manage and share groups of related Fragments. Users navigate
Collections when selecting Fragments to add to a page. To see examples,
the admin page shows all the out-of-the-box Fragments (and their code).
You can create and manage Fragments and Collections without using any
external tools, but you can also use your preferred web development
tools. For an explanation of Fragment creation using Liferay's built in
tools, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-fragments}{Creating
a Fragment}.
\section{Making a Fragment
Configurable}\label{making-a-fragment-configurable}
\noindent\hrulefill
\textbf{Note:} Defining configurations for Page Fragments is available
in Liferay DXP 7.2 SP1+ and Liferay Portal GA2+.
\noindent\hrulefill
Page Fragments are also configurable: defining configuration options for
your fragment eliminates having to maintain multiple other fragments
similar in style. For example, if you want a dark background banner and
a light background banner, you can create one banner with a
configuration option for background type.
The following field types are supported for Fragment configurations:
\begin{itemize}
\tightlist
\item
\texttt{checkbox}
\item
\texttt{colorPalette}
\item
\texttt{itemSelector}
\item
\texttt{select}
\item
\texttt{text}
\end{itemize}
This is available for all Fragment types (e.g., Fragment Renderer,
etc.).
For more information, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-a-fragment-configurable}{Making
a Fragment Configurable}.
\section{Fragments CLI}\label{fragments-cli}
To streamline fragment development, 7.0 provides command line tools for
generating, importing, and exporting fragments and fragment collections.
For more information about the CLI, see the
\href{https://github.com/liferay/generator-liferay-fragments/blob/master/README.md}{official
Liferay Fragments CLI project} reference. Using this CLI is also covered
in
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments-desktop-tools}{Developing
a Fragment using Desktop Tools}.
\section{Contributed Collections}\label{contributed-collections}
Most of the time, Page Fragments are created and imported through the
Page Fragments interface or created directly using the built-in tools.
Any user with the right permissions can update or edit Page Fragments
created like this. You may have certain situations, however, where you
want 100\% static fragments that cannot be modified. In this case you
can create a Contributed Fragment Collection.
Contributed Fragment Collections are deployable modules containing Page
Fragments. Those fragments can be used just like regular fragments, but
are not contained in the database, and cannot be modified except by
updating the module they came from. Use the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-contributed-fragment-collection}{Creating
Contributed Collections guide} to learn to create your own Contributed
Collections.
\section{Fragment Specific Tags}\label{fragment-specific-tags}
In addition to standard HTML, CSS, and JavaScript you can use
Liferay-specific tags to make editable sections or embed widgets in your
Fragment.
Editable elements can be modified before publication. This means that
web developers can create simple, reusable fragments that have identical
formatting, but contain elements that are adaptable to the specific
context.
You can make text, images, and links in a fragment editable by using an
\texttt{\textless{}lfr-editable\textgreater{}} tag. The
\texttt{\textless{}lfr-editable\textgreater{}} tag requires a unique
\texttt{id}, a type, and some content of the specified type inside.
The following four \texttt{type} options are available in an
\texttt{lfr-editable} tag:
\texttt{text}: Creates a space for editable plain text.
\texttt{image}: Must contain a valid
\texttt{\textless{}img\textgreater{}} tag which can then be replaced
with an image before publishing---including those from Documents and
Media.
\texttt{rich-text}: Provides rich text formatting, such as bold,
italics, underline, links, and predefined styles.
\texttt{link}: Must contain a valid anchor tag for which the style,
target URL, and link text can be edited before publishing.
The text or images you provide here are the default values for the
fields. You may want to display them in the final version of the page,
or you may want filler text that should be replaced before the page is
published.
All of these work together to help you create dynamic, reusable elements
for building a site. For example, if you need a small text box with an
image and link to provide a product description, you can create a
fragment containing editable filler text, space for an editable image,
the appropriate formatting, and an editable link. That fragment can be
added to multiple pages, and marketers can define the image, text, and
link for each product they need to describe.
You can make a Fragment even more dynamic by including a widget.
Currently, portlets are the only embeddable types of widgets, but other
options are planned.
You can find a complete list and usage examples of these in the
\href{/docs/7-2/reference/-/knowledge_base/r/fragment-specific-tags}{Page
Fragments Reference}.
\section{Recommendations and Best
Practices}\label{recommendations-and-best-practices}
In general all your code should be semantic and highly reusable. A main
concern is making sure that everything is namespaced properly so it
won't interfere with other elements on the page outside of the Fragment.
\section{CSS}\label{css}
While you can write any CSS in a fragment, it's recommended to prefix it
with a class specific to the fragment to avoid impacting other
fragments. To facilitate this, when creating a new fragment, the HTML
includes a \texttt{div} with an automatically generated class name and
the CSS shows a sample selector using that class. Use it as the basis
for all selectors you add.
\section{JavaScript}\label{javascript}
Avoid adding a lot of JavaScript code, since it isn't easily reusable.
Instead, reference external JS libraries.
\chapter{Developing Fragments}\label{developing-fragments}
This tutorial assumes you understand Fragments and Collections. If you
don't, read the
\href{/docs/7-2/user/-/knowledge_base/u/creating-page-fragments}{Creating
Page Fragments} article first. Once, you're ready, start by creating a
Collection:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
From the menu for your selected site, click \emph{Site Builder} →
\emph{Page Fragments}.
\item
Create a new Collection named \emph{Developing Fragments}.
\end{enumerate}
First, you'll create a \emph{Section}.
\section{Creating a Section}\label{creating-a-section}
The list of Collections appears on the left in the Page Fragments page.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Ensure that you are in the \emph{Developing Fragments} collection.
\item
Click the \emph{New} button \includegraphics{./images/icon-add.png}
and select \emph{Section}.
\item
Name your Section \emph{Basic Section}
\end{enumerate}
You're now on the Fragment editing page. There are four panes on this
screen. You enter HTML in the top left pane, CSS in the top right,
JavaScript in the bottom left, and preview the results in the bottom
right. The Fragment Editor even comes with autocomplete functionality!
\begin{figure}
\centering
\includegraphics{./images/fragment-editor-autocomplete.png}
\caption{The Fragment editor provides autocomplete for Liferay Fragment
specific tags.}
\end{figure}
You can look at the three editing panes as if each were writing to a
separate file. Everything in the HTML pane goes to \texttt{index.html},
the CSS pane goes to \texttt{index.css}, and the JavaScript pane goes to
\texttt{index.js}. The preview pane renders everything as it looks on
the page.
\noindent\hrulefill
\textbf{Warning:} Including images inline in base64 in your Page
Fragments can increase publishing, import, and export times for pages
using those Fragments. Use
\href{/docs/7-2/frameworks/-/knowledge_base/f/including-default-resources-in-fragments}{references
to resources} in your Page Fragments instead.
\noindent\hrulefill
A Section defines a work space. Now create a section with an editable
rich text area where content can be entered:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following code inside the HTML pane:
\begin{verbatim}
Banner Title Example
This is a simple banner component that you can use must provide extra information.
Go Somewhere
\end{verbatim}
\item
Replace the code in the CSS pane with the following:
\begin{verbatim}
.banner {
background-color:#415fa9;
background-position: center;
background-size: cover;
}
\end{verbatim}
\item
Click \emph{Publish} to save your work and make it available to add to
a content page.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} When you start typing the name of a tag, the HTML editor
provides auto-completion for \texttt{lfr} tags like editable elements
and embeddable widgets.
\noindent\hrulefill
As you work, you can observe your changes in the preview pane.
\begin{figure}
\centering
\includegraphics{./images/fragment-editor-basic.png}
\caption{The Fragment editor with HTML and CSS code and a live preview.}
\end{figure}
\section{Creating a Component}\label{creating-a-component}
Components are simple, reusable elements for building parts of a page.
Next create a button with a link as a Component:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go back to the \emph{Site Builder} → \emph{Page Fragments} page and
select the \emph{Developing Fragments} Collection.
\item
Click the \emph{New} button \includegraphics{./images/icon-add.png}
and select \emph{Component}.
\item
Name it \emph{Basic Component}.
\item
Back in the editor, add the following code inside the HTML pane:
\begin{verbatim}
\end{verbatim}
\item
Click \emph{Publish} to save your work and make it available to add to
a content page.
\end{enumerate}
This fragment did not require any CSS. For the button link, no target is
provided by default, so the link must be configured when it is added to
the page.
From here, the Fragment can be added to a Page. To see this process in
action, see the
\href{/docs/7-2/user/-/knowledge_base/u/building-content-pages}{Building
Content Pages} article.
\chapter{Making a Fragment
Configurable}\label{making-a-fragment-configurable-1}
\noindent\hrulefill
\textbf{Note:} Defining configurations for Page Fragments is available
in Liferay DXP 7.2 SP1+ and Liferay Portal GA2+.
\noindent\hrulefill
Defining configuration options for a Fragment gives it more flexibility,
reducing the number of Fragments you must maintain. To make a Fragment
configurable,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the \emph{Site Builder} → \emph{Page Fragments} page.
\item
Click the \emph{Actions} button
(\includegraphics{./images/icon-actions.png}) → \emph{Edit} for the
Fragment (Section or Component) you want to make configurable.
\item
Select the \emph{Configuration} tab at the top of the page.
\begin{figure}
\centering
\includegraphics{./images/fragment-config-tab.png}
\caption{Switch from the Code tab to the Configuration tab to create
your configuration logic.}
\end{figure}
\item
In the editor, add your JSON code. This code is added to your
fragment's \texttt{index.json} file. For example, the code below
provides the \texttt{select} option to choose \emph{dark} or
\emph{light} for a Fragment's heading:
\begin{verbatim}
{
"fieldSets": [
{
"label": "heading",
"fields": [
{
"name": "headingAppliedStyle",
"label": "applied-style",
"description": "this-is-the-style-that-will-be-applied",
"type": "select",
"dataType": "string",
"typeOptions": {
"validValues": [
{
"value": "dark"
},
{
"value": "light"
}
]
},
"defaultValue": "light"
}
]
}
]
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** The `label` property is optional. If it's left out, your
configuration option has no title.
\end{verbatim}
\noindent\hrulefill
\noindent\hrulefill
\begin{verbatim}
**Note:** If your configuration is invalid, you can't save the
code. Be sure to always have a valid JSON configuration before previewing
or saving it.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
The configuration values selected by the user are made available to the
Fragment developer through the FreeMarker context. A configuration value can
be referenced using the notation `${configuration.[fieldName]}`. For the
example snippet above, `${configuration.headingAppliedStyle}` returns
`dark` or `light` depending on the configuration value selected by the user.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\item
You can refer to your Fragment's configuration values in its HTML file
(e.g., \texttt{index.html}). Navigate back to the \emph{Code} tab at
the top of the page and add your HTML. For example,
\begin{verbatim}
[#if configuration.headingAppliedStyle == 'dark']
...
[#else]
...
[/#if]
\end{verbatim}
Configuration values inserted into the FreeMarker context honor the
defined \texttt{datatype} value specified in the JSON file. Therefore,
for this example,
\texttt{configuration.headingAppliedStyle?is\_string} is
\texttt{true}.
\item
Click \emph{Publish} to save your work and make it available to add to
a Content Page.
\begin{figure}
\centering
\includegraphics{./images/fragment-lang-keys.png}
\caption{You can click your Fragment to view its configuration
options.}
\end{figure}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} You can also make a Fragment configurable by leveraging
the Fragments Toolkit. You can create/modify the Fragment's
configuration JSON file and then reimport the Fragment to your Liferay
instance. For more information, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments-desktop-tools}{Page
Fragment Desktop Tools}.
\noindent\hrulefill
Although this example highlights accessing configuration values in HTML
via the FreeMarker context, you can also access these values via
JavaScript. JavaScript configuration objects are named the same as their
FreeMarker counterparts.
For example, a configuration object could be built like this:
\begin{verbatim}
var configuration = {
field1: value1,
field2: value2
}
\end{verbatim}
Another example of setting the configuration object and using it is
shown below:
\begin{verbatim}
const configurationValue = configuration.field1
console.log(configurationValue);
\end{verbatim}
For more examples of Fragment configuration, see
\href{/docs/7-2/reference/-/knowledge_base/r/fragment-configuration-types}{Fragment
Configuration Types}.
Awesome! You now have a configurable Fragment!
\chapter{Managing Fragments and
Collections}\label{managing-fragments-and-collections}
After you create Collections and Fragments, you have a handful of
options for managing them. You'll explore several management options
next.
\section{Collections Management Menu}\label{collections-management-menu}
To access the collections management menu,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select the Collection you want to manage from the \emph{Collections}
list.
\item
Click on the \includegraphics{./images/icon-actions.png} menu next to
the collection name.
\item
Select whether you want to \emph{Edit}, \emph{Export}, \emph{Import},
or \emph{Delete} the collection.
\textbf{Edit}: change the name or description for the collection.
\textbf{Export}: download a \texttt{.zip} file containing the full
collection.
\textbf{Import}: select a \texttt{.zip} file to upload with additional
Fragments.
\textbf{Delete}: remove the current collection and all of its
contents.
\end{enumerate}
Next, you'll learn about the Fragment Management Menu.
\section{Fragment Management Menu}\label{fragment-management-menu}
To access the fragment management menu,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select the Collection containing the Fragment you want to manage from
the \emph{Collections} list.
\item
Click on the \includegraphics{./images/icon-actions.png} menu next to
the Fragment name.
\item
Select whether you want to \emph{Edit}, \emph{Rename}, \emph{Move},
\emph{Make a Copy}, \emph{Change Thumbnail} \emph{Export}, or
\emph{Delete}.
\end{enumerate}
Did you know you can enable automatic propagation for Fragments? You'll
do this next.
\section{Propagating Fragment Changes
Automatically}\label{propagating-fragment-changes-automatically}
\noindent\hrulefill
\textbf{Note:} Propagating Fragment changes is available in Liferay DXP
7.2 SP1+ and Liferay Portal GA2+.
\noindent\hrulefill
By default, when a Fragment developer makes a change to an existing
fragment, the change is not automatically propagated to the pages that
were using it. This gives marketers and page authors more control over
the pages they own, avoiding unexpected changes. For example, if three
pages were using the same Fragment, an update to the Fragment could
introduce unintended changes to some of the pages using it. While this
is a safeguard for the production environment, developers must
\href{/docs/7-2/user/-/knowledge_base/u/propagation-of-changes}{manually
propagate Fragment changes} during testing, which can be tedious. To
give developers more freedom, you can enable automatic propagation for
Fragment changes:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the Control Panel → \emph{Configuration} → \emph{System
Settings} → \emph{Page Fragments}.
\item
Enable the checkbox \emph{Propagate Fragment Changes Automatically}.
\item
Click \emph{Save}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/fragment-propagation.png}
\caption{Once Fragment propagation is enabled, developers can
automatically propagate Fragment changes to all pages using them.}
\end{figure}
Great! You've enabled Fragment propagation system wide! Now when a
developer publishes a Fragment, the changes apply immediately to all
Content Pages, Content Page Templates, and Display Page Templates using
it, overwriting existing Fragment code. Automatic propagation works only
for HTML, CSS, and JS Fragment code, not the editable values.
\noindent\hrulefill
\textbf{Note:} It's recommended to only leverage this functionality
during testing, as automatic propagation on the production environment
can cause unintended consequences.
\noindent\hrulefill
When using the Fragment Editor, you're now notified that automatic
Fragment propagation is enabled.
\begin{figure}
\centering
\includegraphics{./images/fragment-propagation-info.png}
\caption{You're notified when automatic propagation is enabled.}
\end{figure}
Now that you've seen how to use Liferay's built-in tools to manage
Fragments, you can see how to do it using your own tools of choice and
the Fragments Toolkit.
\chapter{Developing A Fragment Using Desktop
Tools}\label{developing-a-fragment-using-desktop-tools}
You can develop a fragment using any preferred desktop tools. Since the
Fragment is HTML, CSS, and JavaScript, you could use a text editor or a
specialized tool with its own built in previews.
\section{Collection Format}\label{collection-format}
To import a Collection into Liferay DXP, it must be archived in a
\texttt{.zip} with the contents in the following format:
\begin{itemize}
\item
\texttt{collection.json}: a text file which describes your collection
with the format
\texttt{\{"name":"\textless{}collection-name\textgreater{}","description":"\textless{}collection-description\textgreater{}"\}}.
\item
\texttt{language.properties}: the language keys defined for the
collection.
\begin{itemize}
\item
\texttt{{[}fragment-name{]}/}: a folder containing all of the files
for a single Page Fragment.
\begin{itemize}
\item
\texttt{fragment.json}: a text file that describes a Page Fragment
with the format
\begin{verbatim}
{
"cssPath": "index.css",
"configurationPath": "index.json",
"htmlPath": "index.html",
"jsPath": "index.js",
"name": "",
"type": ""
}
\end{verbatim}
Update the \texttt{*Path} properties in your
\texttt{fragment.json} file if you change the names of your
\texttt{index.*} files.
\item
\texttt{index.css}: the CSS source for the fragment.
\item
\texttt{index.html}: the HTML source for the fragment.
\item
\texttt{index.json}: a JSON file that defines the fragment's
configuration.
\item
\texttt{index.js}: the JavaScript source for the fragment.
\item
\texttt{thumbnail.png}: the thumbnail that will display when the
Fragment is displayed in a list.
\end{itemize}
\item
\texttt{{[}resources{]}/}: a folder containing any additional images
or other external files needed for the fragment.
\end{itemize}
\end{itemize}
A collection can contain any number of fragments, so you can have
multiple subfolders in the collection. This format is the same as what's
exported from within Liferay. If you import a \texttt{.zip} file that is
not organized like this, any fragments that are found will be imported
into special collection called \emph{Imported} which is created for
orphaned fragments.
\section{Fragment CLI}\label{fragment-cli}
You can manage Fragment creation and deployment manually, or you can use
Liferay's Fragment CLI:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Follow the project instructions to
\href{https://github.com/liferay/generator-liferay-fragments/blob/master/README.md}{set
up the Fragments Toolkit}.
\item
Run \texttt{yo\ liferay-fragments}.
\item
Follow the prompts to create a fragment.
\end{enumerate}
Now you will have the basic structure created, but there's still more
that the Fragments Toolkit can help you with.
\noindent\hrulefill
\textbf{Note:} You can see all of the available tasks inside the
\texttt{scripts} section in the Fragment CLI \texttt{package.json}.
\noindent\hrulefill
\section{Creating Collections}\label{creating-collections}
Before you can create any Page Fragments, you must create a Collection.
You can learn more about Collections in the
\href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages\#creating-page-fragments}{Creating
Page Fragments} section. Creating a Collection will create the base
folder structure and some information about your Collection. To do this,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
From inside of your project, run \texttt{npm\ run\ add-collection}.
\item
Follow the prompts to name your Collection.
\end{enumerate}
You can now create Page Fragments inside of this Collection.
\section{Creating Fragments}\label{creating-fragments}
A Page Fragment is made up of three primary files, \texttt{index.html},
\texttt{index.css}, and \texttt{index.js}. However, the files must be
properly arranged in the folder structure and have the appropriate
metadata to be imported onto your server. The Fragments Toolkit will
create the files in the correct hierarchy with all of the necessary
information.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
From inside of the Collection you created, run
\texttt{npm\ run\ add-fragment}.
\item
Follow the prompts to add the necessary information about your Page
Fragment.
\end{enumerate}
Now the files are all created and you can edit them using your editor of
choice.
\section{Importing and Exporting
Fragments}\label{importing-and-exporting-fragments}
The Fragments Toolkit can connect to your currently running Liferay DXP
to import and export fragments. You can even have fragments that you
create with the toolkit imported into Liferay DXP automatically.
\begin{itemize}
\item
To get collections and fragments from a running server, run
\texttt{npm\ run\ export}.
\item
To send the collections and fragments from your current project to a
running server, run \texttt{npm\ run\ import}. If your Fragment's
configuration JSON (if available) is invalid, the import fails and
provides an error message.
\item
To have collections and fragments automatically imported into Liferay
DXP as they are created or modified, run
\texttt{npm\ run\ import:watch}.
\item
To preview how a fragment will look when it's imported, run
\texttt{npm\ run\ preview}. This renders a fragment on a specified
Liferay server without importing it. When changes are made to the
fragment while it's previewed, changes are auto reloaded to rapidly
display updates. Note, this is available for Liferay DXP 7.2 SP1+ and
Liferay Portal 7.2 GA2+. You must install the
\href{https://web.liferay.com/marketplace/-/mp/application/109571986}{OAuth
2} plugin in your portal instance for this command to work properly.
\item
To create a \texttt{.zip} file that can be manually imported into
Liferay DXP, run \texttt{npm\ run\ compress}.
\end{itemize}
With these tools at your disposal, you can more efficiently manage
creating and editing Page Fragments with whatever tools and environments
work best for you.
\chapter{Creating a Contributed Fragment
Collection}\label{creating-a-contributed-fragment-collection}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
To create a Contributed Fragment Collection, a developer must,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a module which will contain the necessary logic and the
fragments.
\item
Extend the class \texttt{BaseFragmentCollectionContributor} with all
the logic for reading the contributed fragments.
\item
Add fragments as resources in the module.
\end{enumerate}
Once you deploy the module, any fragments contained in it will be
available for use.
To better understand Contributed Fragment Collections, create one called
\texttt{DemoFragmentCollectionContributor}.
\section{Create a Module}\label{create-a-module}
First you must create the module in your development environment. Follow
the instructions in
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Creating
a Project}.
\section{Create the Java Class}\label{create-the-java-class}
Next, you must create the Java package and class to handle the logic for
the contributed collection:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a package in your module named
\texttt{com.liferay.fragment.collection.contributor.demo}
\item
Inside of that package, create a Java class named
\texttt{DemoFragmentCollectionContributor} that extends
\texttt{BaseFragmentCollectionContributor}.
\item
Above the class declaration, add the \texttt{@Component} annotation to
set the service class:
\begin{verbatim}
@Component(service = FragmentCollectionContributor.class)
\end{verbatim}
\item
Create the variable for the servlet context:
\begin{verbatim}
private ServletContext _servletContext;
\end{verbatim}
\item
Define the \texttt{getFragmentCollectionKey()} and
\texttt{getServletContext()} methods:
\begin{verbatim}
@Override
public String getFragmentCollectionKey() {
return "DEMO";
}
@Override
public ServletContext getServletContext() {
return _servletContext;
}
\end{verbatim}
\item
Below that use the \texttt{@Reference} annotation to define your
module's symbolic name:
\begin{verbatim}
@Reference(
target = "(osgi.web.symbolicname=com.liferay.fragment.collection.contributor.demo)"
)
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** `osgi.web.symbolicname` must match `Bundle-SymbolicName` from `bnd.bnd`
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{6}
\tightlist
\item
Organize your imports and save.
\end{enumerate}
\section{Create the Resources}\label{create-the-resources}
Next you need to include the fragments that you want to contribute in
your module:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
In your module's \texttt{resources/} folder, create the folder
structure
\texttt{/com/liferay/fragment/collection/contributor/demo/dependencies}.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** The class package name and resources package name must match
(e.g. `[my.class.package.structure].dependencies`).
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Copy the Fragments you want to distribute into the folder. You can
learn how to create a Fragment in the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-fragments}{Creating
Fragments section}.
\item
Create a file named \texttt{collection.json} in the same folder with
this format:
\begin{verbatim}
{
"fragments": [
"[fragment-1]",
"[fragment-2]",
"[fragment-3]",
...
],
"name": "[collection-name]"
}
\end{verbatim}
If a fragment is not listed in \texttt{collection.json}, it will not
be available in the Contributed Collection, even if the files are
included in the module.
\end{enumerate}
Next, you'll configure the module's metadata so the fragments are
imported.
\section{Configuring the Metadata}\label{configuring-the-metadata}
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your bundle's \texttt{bnd.bnd} file and add the
\texttt{Web-ContextPath} header to point to your bundle's folder so
the fragment resources are loaded properly:
\begin{verbatim}
Web-ContextPath: /my-fragment-collection-contributor
\end{verbatim}
\item
Add the \texttt{-dsannotations-options} instruction and set it to use
the \texttt{inherit} option. This specifies to use DS annotations
found in the class hierarchy of the component class:
\begin{verbatim}
-dsannotations-options: inherit
\end{verbatim}
\end{enumerate}
Next, you'll dive into providing thumbnail images and language
keys/translations.
\section{Providing Thumbnail Images}\label{providing-thumbnail-images}
You can also provide thumbnail images for reference for your fragments:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Under \texttt{resources/META-INF/resources} create a folder named
\texttt{thumbnails}.
\item
Copy thumbnail images into the folder with the format
\texttt{{[}fragment-name{]}.png} for each fragment.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} All fragments added through a Contributed Fragment
Collection will be available globally to all Sites.
\noindent\hrulefill
\section{Providing Language Keys}\label{providing-language-keys}
Providing language keys in your Fragment gives you the option for
translating the text you display. Here's how to do it:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
You must define your language keys in the Fragment's collection
folder. Create the
\texttt{{[}COLLECTION{]}/src/main/resources/content/Language.properties}
file.
\item
Add your language keys. For example,
\begin{verbatim}
applied-style=Applied Style
this-is-the-style-that-will-be-applied=This is the style that will be applied.
dark=Dark
light=Light
\end{verbatim}
\end{enumerate}
You can learn more about providing translations in the
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{Localizing
Your Application} article.
\section{Deploy the Contributed Fragment
Collection}\label{deploy-the-contributed-fragment-collection}
Now that you have created the necessary pieces of the module, you can
build it and
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploy
it} to Liferay DXP. After it's deployed, the Fragments will be available
for use. This can also be done by using the
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments-desktop-tools\#importing-and-exporting-fragments}{Fragments
Toolkit}. Contributed Fragments cannot be edited with Liferay, and can
only be updated by editing the fragments in your module and the building
and redeploying them.
\chapter{Including Default Resources in
Fragments}\label{including-default-resources-in-fragments}
When creating Page Fragments, you can upload resources (e.g., images,
documents, etc.) to your Fragment Collection to make them always
available from the Collection, rather than relying on resources uploaded
in other areas of your Site (e.g., Documents and Media). For more
information on how to include resources in your Fragment Collection from
the Page Fragments interface, see
\href{/docs/7-2/user/-/knowledge_base/u/creating-page-fragments}{Creating
Page Fragments}.
Once you've uploaded your resource to a Fragment Collection, you can
specify it in your Fragment:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the Fragment editing page by clicking your Fragment's
\emph{Actions} \includegraphics{./images/icon-actions.png} →
\emph{Edit} button.
\item
Specify the image by using this syntax:
\texttt{{[}resources:IMAGE\_NAME{]}}. For example, you could include
an image \texttt{building.png} within an HTML image tag like this:
\begin{verbatim}
\end{verbatim}
You can view a full example snippet below:
\begin{verbatim}
\end{verbatim}
\item
Add any additional HTML, CSS, or JavaScript to your Fragment and then
click \emph{Publish}.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can also reference your Fragment Collection's resources
in your CSS code too. It follows the same syntax as its HTML.
\end{verbatim}
\noindent\hrulefill
\begin{figure}
\centering
\includegraphics{./images/fragment-resources.png}
\caption{Any Fragment from the Fragment Collection has access to the
uploaded resources.}
\end{figure}
Great! You've successfully referenced a default resource from your
Fragment Collection!
\chapter{Supporting Custom Content Types in Content and Display
Pages}\label{supporting-custom-content-types-in-content-and-display-pages}
Content Pages and Display Page Templates can display several types of
content out-of-the-box:
\begin{itemize}
\tightlist
\item
Web Content Article
\item
Document
\item
Blogs Entry
\end{itemize}
You can publish these content types in highly customizable ways using
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{Page
Fragments}. You can use these page types to map fields of certain
content (e.g., Web Content) to fields defined in a Page Fragment. Then
you can publish the content on a page using the Page Fragment as a
template. To see an example of how Display Page Templates work, see the
\href{/docs/7-2/user/-/knowledge_base/u/display-page-template-example}{Display
Page Template Example}. For more info on creating Content Pages, see the
\href{/docs/7-2/user/-/knowledge_base/u/building-content-pages}{Building
Content Pages} article.
If you want to extend the Content Page or Display Page Template
framework to support other content types, you must use the
\href{/docs/7-2/frameworks/-/knowledge_base/f/the-info-framework}{Info
framework}.
You must complete the following steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Provide basic information about your custom content type.
\item
Provide your content type's fields so they're configurable in the Page
Editor.
\item
Provide friendly URLs for your page type.
\item
Handle the information that the user is requesting.
\end{enumerate}
As an example, you'll step through how to provide this information to
the Content Page and Display Page Template frameworks.
\noindent\hrulefill
\textbf{Note:} This section assumes you're customizing Display Page
Templates' available content types. The same process outlined in these
articles also applies to Content Pages, although it's not explicitly
stated.
\noindent\hrulefill
Continue on to begin!
\chapter{Mapping a Content Type to a
Page}\label{mapping-a-content-type-to-a-page}
You must allow the mapping of your custom content type to the page type.
To do this, implement the
\href{https://docs.liferay.com/dxp/apps/info/2.0.0/javadocs/com/liferay/info/display/contributor/InfoDisplayContributor.html}{\texttt{InfoDisplayContributor}}
interface. Follow the steps below to complete this for the custom User
content type.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside your custom model project, create a new Java package and add a
class named \texttt{UserInfoDisplayContributor}.
\item
Implement the \texttt{InfoDisplayContributor} interface and pass the
\texttt{User} model as the type parameter. Then add the
\texttt{@Component} annotation:
\begin{verbatim}
@Component(immediate = true, service = InfoDisplayContributor.class)
public class UserInfoDisplayContributor
implements InfoDisplayContributor {
}
\end{verbatim}
The \texttt{@Component} annotation registers the class as an info
display contributor in the OSGi service registry. Set the
\texttt{service} property to the interface you're implementing.
\item
Implement the methods. For the example User content type, three
methods are crucial to mapping its model to the Display Page Template
framework:
\begin{verbatim}
@Override
public String getClassName() {
return User.class.getName();
}
@Override
public String getInfoURLSeparator() {
return "/user/";
}
@Override
public String getLabel(Locale locale) {
return "Users";
}
\end{verbatim}
\begin{itemize}
\tightlist
\item
The class name is used to link the Display Page Template to the User
model.
\item
The URL separator is used to generate friendly URLs for the Display
Page Template.
\item
The label is the display name for the new content type.
\end{itemize}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/custom-model-selectable.png}
\caption{After creating the \texttt{*InfoDisplayContributor} class, you
can create Display Page Templates and map them to your custom model.}
\end{figure}
Great! You've mapped your custom content type to the Display Page
Template framework. Next, you'll provide your content type's fields.
\chapter{Specifying the Fields of a Custom Content
Type}\label{specifying-the-fields-of-a-custom-content-type}
Now that your custom content type is selectable for a Display Page
Template, you must specify the fields you want the user to map to the
fragment's editable fields in the Display Page Template. To do this,
implement the
\href{https://docs.liferay.com/dxp/apps/info/2.0.0/javadocs/com/liferay/info/display/contributor/field/InfoDisplayContributorField.html}{\texttt{InfoDisplayContributorField}}
interface.
Follow the steps below to create a user name field for the User content
type:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside your custom model project, add a class named
\texttt{UserNameInfoDisplayContributorField}.
\item
Implement the \texttt{InfoDisplayContributorField} interface and pass
the \texttt{User} model as the type parameter. Then add the
\texttt{@Component} annotation:
\begin{verbatim}
@Component(
property = "model.class.name=com.liferay.portal.kernel.model.User",
service = InfoDisplayContributorField.class
)
public class UserNameInfoDisplayContributorField
implements InfoDisplayContributorField {
}
\end{verbatim}
The \texttt{@Component} annotation declares the class as an info
display contributor field in the OSGi service registry. You also set
the property \texttt{model.class.name}, which associates the content
type you wish to configure with this service.
\item
Implement the methods.
\begin{verbatim}
@Override
public String getKey() {
return "userName";
}
@Override
public String getLabel(Locale locale) {
return "User Name";
}
@Override
public InfoDisplayContributorFieldType getType() {
return InfoDisplayContributorFieldType.TEXT;
}
@Override
public String getValue(User user, Locale locale) {
return user.getFullName();
}
\end{verbatim}
The above methods
\begin{itemize}
\tightlist
\item
set the content type field key to \texttt{username}.
\item
set the field label to \texttt{User\ Name}.
\item
set the field type to text.
\item
set the field value to the user's full name.
\end{itemize}
\item
Now you must override the \texttt{getInfoDisplayFields} method in your
\texttt{*DisplayContributor} class, so the mappable fields are
displayed. Open the \texttt{UserInfoDisplayContributor} class and add
the following method:
\begin{verbatim}
@Override
public Set getInfoDisplayFields(
long classTypeId, Locale locale)
throws PortalException {
Set infoDisplayFields = new LinkedHashSet<>();
List infoDisplayContributorFields =
_infoDisplayContributorFieldTracker.getInfoDisplayContributorFields(
getClassName());
for (InfoDisplayContributorField infoDisplayContributorField :
infoDisplayContributorFields) {
InfoDisplayContributorFieldType infoDisplayContributorFieldType =
infoDisplayContributorField.getType();
infoDisplayFields.add(
new InfoDisplayField(
infoDisplayContributorField.getKey(),
infoDisplayContributorField.getLabel(locale),
infoDisplayContributorFieldType.getValue()));
}
return infoDisplayFields;
}
@Reference
private InfoDisplayContributorFieldTracker _infoDisplayContributorFieldTracker;
\end{verbatim}
This method references your new \texttt{*InfoDisplayContributorField}
class to specify your content type's fields.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/content-type-custom-fields.png}
\caption{After creating the \texttt{*InfoDisplayContributorField} class,
your custom content type has a new field to map.}
\end{figure}
Awesome! You've mapped the content type's fields to the editable fields
of the provided fragments. Next, you'll provide the friendly URLs for
the Display Page Template.
\chapter{Providing Friendly URLs for a Custom Content
Type}\label{providing-friendly-urls-for-a-custom-content-type}
To provide a friendly URL for your custom content type, you must
implement a friendly URL resolver to retrieve the desired information.
For the User content type example, you'll want to retrieve profiles by a
user's screen name.
To do this, follow the steps below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the \texttt{*InfoDisplayContributor} class you created previously
(covered
\href{/docs/7-2/frameworks/-/knowledge_base/f/mapping-a-content-type-to-a-page}{here}).
Implement the \texttt{getInfoDisplayObjectProvider} method. For the
User example, it looks like this:
\begin{verbatim}
@Override
public InfoDisplayObjectProvider getInfoDisplayObjectProvider(
long groupId, String urlTitle)
throws PortalException {
Group group = _groupLocalService.getGroup(groupId);
User user = _userLocalService.getUserByScreenName(
group.getCompanyId(), urlTitle);
return new InfoDisplayObjectProvider() {
@Override
public long getClassNameId() {
return _classNameLocalService.getClassNameId(getClassName());
}
@Override
public long getClassPK() {
return user.getUserId();
}
@Override
public long getClassTypeId() {
return 0;
}
@Override
public String getDescription(Locale locale) {
return StringPool.BLANK;
}
@Override
public User getDisplayObject() {
return user;
}
@Override
public long getGroupId() {
return groupId;
}
@Override
public String getKeywords(Locale locale) {
return StringPool.BLANK;
}
@Override
public String getTitle(Locale locale) {
return user.getFullName();
}
@Override
public String getURLTitle(Locale locale) {
return user.getScreenName();
}
};
}
\end{verbatim}
This method returns a new \texttt{InfoDisplayObjectProvider} for the
User type. This is the specific model instance used to retrieve the
mapped values and check for the display page. This is required by the
friendly URL resolver. Now you'll implement the friendly URL resolver
for the User content type.
\item
Inside your custom model project, add a class named
\texttt{UserDisplayPageFriendlyURLResolver}.
\item
Implement the
\href{https://docs.liferay.com/dxp/portal//7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/FriendlyURLResolver.html}{\texttt{FriendlyURLResolver}}
interface. Then add the \texttt{@Component} annotation:
\begin{verbatim}
@Component(service = FriendlyURLResolver.class)
public class UserDisplayPageFriendlyURLResolver implements FriendlyURLResolver {
}
\end{verbatim}
The \texttt{@Component} annotation declares the class as a friendly
URL resolver in the OSGi service registry.
\item
Implement the methods. The User type's implementation looks like this:
\begin{verbatim}
@Override
public String getActualURL(
long companyId, long groupId, boolean privateLayout,
String mainPath, String friendlyURL, Map params,
Map requestContext)
throws PortalException {
HttpServletRequest request = (HttpServletRequest)requestContext.get(
"request");
InfoDisplayContributor infoDisplayContributor =
_getInfoDisplayContributor();
List paths = StringUtil.split(friendlyURL, CharPool.SLASH);
InfoDisplayObjectProvider infoDisplayObjectProvider =
infoDisplayContributor.getInfoDisplayObjectProvider(
groupId, paths.get(1));
request.setAttribute(
AssetDisplayPageWebKeys.INFO_DISPLAY_OBJECT_PROVIDER,
infoDisplayObjectProvider);
request.setAttribute(
InfoDisplayWebKeys.INFO_DISPLAY_CONTRIBUTOR,
infoDisplayContributor);
Layout layout = _getInfoDisplayObjectProviderLayout(
infoDisplayObjectProvider);
return _portal.getLayoutActualURL(layout, mainPath);
}
@Override
public LayoutFriendlyURLComposite getLayoutFriendlyURLComposite(
long companyId, long groupId, boolean privateLayout,
String friendlyURL, Map params,
Map requestContext)
throws PortalException {
Layout layout = _getInfoDisplayObjectProviderLayout(
_getInfoDisplayObjectProvider(groupId, friendlyURL));
return new LayoutFriendlyURLComposite(layout, friendlyURL);
}
@Override
public String getURLSeparator() {
return "/user/";
}
private InfoDisplayContributor _getInfoDisplayContributor()
throws PortalException {
InfoDisplayContributor infoDisplayContributor =
_infoDisplayContributorTracker.
getInfoDisplayContributorByURLSeparator(getURLSeparator());
if (infoDisplayContributor == null) {
throw new PortalException(
"Info display contributor is not available for " +
getURLSeparator());
}
return infoDisplayContributor;
}
private InfoDisplayObjectProvider _getInfoDisplayObjectProvider(
long groupId, String friendlyURL)
throws PortalException {
List paths = StringUtil.split(friendlyURL, CharPool.SLASH);
InfoDisplayContributor infoDisplayContributor =
_infoDisplayContributorTracker.getInfoDisplayContributor(
User.class.getName());
return infoDisplayContributor.getInfoDisplayObjectProvider(
groupId, paths.get(1));
}
private Layout _getInfoDisplayObjectProviderLayout(
InfoDisplayObjectProvider infoDisplayObjectProvider) {
LayoutPageTemplateEntry layoutPageTemplateEntry =
_layoutPageTemplateEntryService.fetchDefaultLayoutPageTemplateEntry(
infoDisplayObjectProvider.getGroupId(),
infoDisplayObjectProvider.getClassNameId(),
infoDisplayObjectProvider.getClassTypeId());
if (layoutPageTemplateEntry != null) {
return _layoutLocalService.fetchLayout(
layoutPageTemplateEntry.getPlid());
}
return null;
}
@Reference
private InfoDisplayContributorTracker _infoDisplayContributorTracker;
@Reference
private LayoutLocalService _layoutLocalService;
@Reference
private LayoutPageTemplateEntryService _layoutPageTemplateEntryService;
@Reference
private Portal _portal;
\end{verbatim}
Notice you're finding the \texttt{InfoDisplayObjectProvider}
corresponding to the current user. This serves as the
representation/descriptor of the mapped object.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** This `FriendlyURLResolver` implementation uses the default
display page template for the User model.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
When this implementation is deployed, you'll receive an empty page when
calling the URL `[host]/web/guest/user/[screenName]`. You must return the
values from the users that are mapped to the display page. You'll do this
next.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\item
Implement the \texttt{getInfoDisplayFieldsValue} method in the
previously created \texttt{*InfoDisplayContributor} class.
\begin{verbatim}
@Override
public Map getInfoDisplayFieldsValues(
User user, Locale locale)
throws PortalException {
Map infoDisplayFieldsValues = new HashMap<>();
List infoDisplayContributorFields =
_infoDisplayContributorFieldTracker.getInfoDisplayContributorFields(
getClassName());
for (InfoDisplayContributorField infoDisplayContributorField :
infoDisplayContributorFields) {
Object fieldValue = infoDisplayContributorField.getValue(
user, locale);
infoDisplayFieldsValues.putIfAbsent(
infoDisplayContributorField.getKey(), fieldValue);
}
return infoDisplayFieldsValues;
}
\end{verbatim}
\end{enumerate}
Great! Now you have a friendly URL that maps to your display page
template's custom content type.
\chapter{Integrating Display Pages into Content
Creation}\label{integrating-display-pages-into-content-creation}
After you add support for Display Pages in your custom entities, you can
integrate display page configuration into your entity's creation form.
\section{Display Page Taglib Example}\label{display-page-taglib-example}
To provide the Display Page selector for the User type after you
\href{/docs/7-2/frameworks/-/knowledge_base/f/specifying-the-fields-of-a-custom-content-type}{created
fields for it},
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your JSP used for displaying the editing interface (e.g.,
\texttt{.../META-INF/resources/.../edit\_entry.jsp}).
\item
Add this code in the appropriate place in the layout to add the
Display Page selector:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Now, a selector is available to define a default Display Page when
editing or creating a User.
\begin{figure}
\centering
\includegraphics{./images/display-pages-select-default-display-page.png}
\caption{You need to add the Display Page selection to your content
type's create/edit page to define the Display Page for each instance of
that asset.}
\end{figure}
Awesome! Your custom content type is now available for Content Pages
and/or Display Page Templates.
\chapter{Screen Navigation Framework}\label{screen-navigation-framework}
The Screen Navigation Framework is for customizing and extending
application UIs. You can use it to make Liferay's applications your own
and to make your applications customizable by others.
The framework uses a specific structure for screens and supports one or
two levels of navigation. Each item in the top level navigation is a
\texttt{ScreenNavigationCategory}. Each item in the second level is a
\texttt{ScreenNavigationEntry}. Categories are usually represented by
tabs, while entries use a second level of navigation. You need not have
any Entries in your application, but you must have at least one
Category.
\begin{figure}
\centering
\includegraphics{./images/screen-nav-sample-screen-1.png}
\caption{The User Management application has three Screen Navigation
Categories: General, Contact, and Preference; and each of those have a
number of Screen Navigation Entries}
\end{figure}
The Screen structure normally renders Navigation Categories as
horizontal tabs at the top of the page and Navigation Entries as a
vertical list of items along the left side of the page. The screen box
containing the content uses the rest of the screen. You can customize
this default layout for your needs.
\begin{figure}
\centering
\includegraphics{./images/screen-nav-one-level.png}
\caption{Many application only use Screen Navigation Categories for
their functionality, and don't have Screen Navigation Entries. For
Blogs, Entries and Images are Categories with no Entries.}
\end{figure}
\section{Using the Framework for Your
Application}\label{using-the-framework-for-your-application}
The Screen Navigation Framework comprises two parts: Java classes for
your screens and a tag library for your front-end. To use Screen
Navigation for your application, first you'll create the necessary Java
classes and then add the front-end support through JSPs.
Your \texttt{ScreenNavigationCategory} class must be a a component that
implements the \texttt{ScreenNavigationCategory} interface with these
methods:
\textbf{\texttt{getCategoryKey()}}: returns the category's primary key.
\textbf{\texttt{getLabel(Locale\ locale)}}: returns the label of the
key.
\textbf{\texttt{getScreenNavigationKey()}}: returns the navigation key
that the category belongs in, as defined in your application.
Your \texttt{ScreenNavigationEntry} class, similarly must be a component
which implements \texttt{ScreenNavigationEntry} with the following
methods:
\textbf{\texttt{getCategoryKey()}}: returns the category's primary key.
\textbf{\texttt{getEntryKey()}}: returns the entry's primary key.
\textbf{\texttt{getLabel()}}: returns the entries label.
\textbf{\texttt{getScreenNavigationKey()}}: returns the navigation key
for the category of the current entry.
\textbf{\texttt{isVisible(User\ user,\ T\ screenModelBean)}}: boolean
for whether or not the entry should be visible for the current user.
\textbf{\texttt{render(HttpServletRequest\ request,\ HttpServletResponse\ response)}}:
renders the entry. The \texttt{render} method is also where any logic
for creating the configuration goes.
\section{Adding Custom Screens to Liferay
Applications}\label{adding-custom-screens-to-liferay-applications}
You can extend certain Liferay Applications with custom screens. Custom
screens can add configuration for features you've developed and added to
a Liferay application, integrating them seamlessly with the original
application.
The parameters it needs are \texttt{key}, \texttt{modelBean}, and
\texttt{portletURL}.
\begin{itemize}
\item
\textbf{Key}: a unique name for the navigation in this application.
\item
\textbf{modelBean}: the model that is being rendered
\item
\textbf{portletURL}: the portlet URL used to build the titles for each
link.
\end{itemize}
In addition to these parameters, you must build the page that users will
use for configuration, and connect it to your business logic through the
render command.
\chapter{Using Screen Navigation for Your
Application}\label{using-screen-navigation-for-your-application}
To use the Screen Navigation framework with your application you must
create a Screen Navigation Category and Entry, and then create the JSP
to provide the layout for the Screen Navigation Entry.
\section{Adding Screens to Your Application's
Back-end}\label{adding-screens-to-your-applications-back-end}
To add screens to your application, first you must add at least one
Navigation Category for the top level navigation. Then you can add
additional Navigation Entries for each page that you need. To
demonstrate this, follow the instructions for integrating Screen
Navigation for a sample application.
First, create the Navigation Category:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your existing application, create a component named
\texttt{SampleScreenNavigationCategory} that implements the
\texttt{ScreenNavigationCategory} interface.
\item
In the \texttt{@Component} declaration, set your priority property
which determines what order items appear in in the side navigation:
\begin{verbatim}
property = "screen.navigation.category.order:Integer=10",
\end{verbatim}
\item
Add the following methods to the class body:
\begin{verbatim}
@Override
public String getCategoryKey() {
return SampleScreenNavigationConstants.
CATEGORY_KEY_SAMPLE_CONFIGURATION;
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "general");
}
@Override
public String getScreenNavigationKey() {
return SampleScreenNavigationConstants.
SAMPLE_KEY_METHOD;
}
\end{verbatim}
When you're finished, your \texttt{ScreenNavigationCategory} class
looks like this:
\begin{verbatim}
@Component(
property = "screen.navigation.category.order:Integer=10",
service = ScreenNavigationCategory.class
)
public class SampleScreenNavigationCategory
implements ScreenNavigationCategory {
@Override
public String getCategoryKey() {
return SampleScreenNavigationConstants.
CATEGORY_KEY_SAMPLE_CONFIGURATION;
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "general");
}
@Override
public String getScreenNavigationKey() {
return SampleScreenNavigationConstants.
SAMPLE_KEY_METHOD;
}
}
\end{verbatim}
\end{enumerate}
Next, add a Navigation Entry:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a component named \texttt{SampleScreenNavigationEntry} which
implements \texttt{ScreenNavigationEntry}.
\item
Create the the \texttt{@Reference} variables you need for the render
logic:
\begin{verbatim}
@Reference
private ConfigurationProvider _configurationProvider;
@Reference
private JSPRenderer _jspRenderer;
@Reference
private Portal _portal;
@Reference(
target = "(osgi.web.symbolicname=com.liferay.commerce.payment.method.sample)"
)
private ServletContext _servletContext;
\end{verbatim}
\item
Implement the following methods in your component:
\begin{verbatim}
@Override
public String getCategoryKey() {
return SampleScreenNavigationConstants.
CATEGORY_KEY_SAMPLE_CONFIGURATION;
}
@Override
public String getEntryKey() {
return SampleScreenNavigationConstants.
ENTRY_KEY_SAMPLE_CONFIGURATION;
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(
locale,
SampleScreenNavigationConstants.
CATEGORY_KEY_SAMPLE_CONFIGURATION);
}
@Override
public String getScreenNavigationKey() {
return SpaceShipScreenNavigationConstants.
SAMPLE_KEY_METHOD;
}
@Override
public boolean isVisible(
User user, SamplePermissions spaceShipPermissions) {
if (samplePermissions.criteriaMethod())
{
return true;
}
return false;
}
@Override
public void render(HttpServletRequest request, HttpServletResponse response)
throws IOException {
_jspRenderer.renderJSP(request, response, "/my-category/view-category.jsp");
}
\end{verbatim}
Here is what the \texttt{SampleScreenNavigationEntry} class looks
like:
\begin{verbatim}
@Component(
property = "screen.navigation.entry.order:Integer=20",
service = ScreenNavigationEntry.class
)
public class
SampleScreenNavigationEntry
implements ScreenNavigationEntry {
public static final String
ENTRY_KEY_SAMPLE_CONFIGURATION =
"sample-configuration";
@Override
public String getCategoryKey() {
return SpaceShipScreenNavigationConstants.
CATEGORY_KEY_SAMPLE_CONFIGURATION;
}
@Override
public String getEntryKey() {
return ENTRY_KEY_SAMPLE_CONFIGURATION;
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(
locale,
SpaceShipScreenNavigationConstants.
CATEGORY_KEY_SAMPLE_CONFIGURATION);
}
@Override
public String getScreenNavigationKey() {
return SpaceShipScreenNavigationConstants.
SAMPLE_KEY_METHOD;
}
@Override
public boolean isVisible(
User user, SamplePermissions spaceShipPermissions) {
if (samplePermissions.criteriaMethod())
{
return true;
}
return false;
}
@Override
public void render(HttpServletRequest request, HttpServletResponse response)
throws IOException {
_jspRenderer.renderJSP(request, response, "/my-category/view-category.jsp");
}
@Reference
private JSPRenderer _jspRenderer;
@Reference(
target = "(osgi.web.symbolicname=com.liferay.commerce.payment.method.sample);
}
\end{verbatim}
\end{enumerate}
You can implement your render method any way that you want as long as it
provides a way to render HTML. Liferay developers typically use JSPs,
shown below.
\section{Adding Screens to Your Application's
Front-end}\label{adding-screens-to-your-applications-front-end}
The \texttt{render} method that you created in your last step references
\texttt{/my-category/view-category.jsp}. Create the JSP now:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In \texttt{/src/resources/META-INF/resources} create the
\texttt{my-category} folder.
\item
Inside of that folder, create \texttt{view-category.jsp}.
\item
Inside the JSP add the \texttt{liferay-frontend:screen-navigation}
taglib with the required parameters:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
After that tag, add the rest of the content of the JSP file to handle
user interactions and communication with the back-end for configuration.
\chapter{Extending Categories
Administration}\label{extending-categories-administration}
The Categories Administration application supports adding Custom Screens
to provide additional options for editing a category. To demonstrate
adding a new Screen Navigation Entry and Category, you'll add one to
Categories Administration.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a new Java class in the \texttt{asset-categories-admin-web}
module named \texttt{CategoryCustomScreenNavigationEntry} that
implements \texttt{ScreenNavigationCategory} and
\texttt{ScreenNavigationEntry}.
\item
Add the following Component annotation above the class declaration:
\begin{verbatim}
@Component(
property = {
"screen.navigation.category.order:Integer=1",
"screen.navigation.entry.order:Integer=1"
},
service = {ScreenNavigationCategory.class, ScreenNavigationEntry.class}
)
\end{verbatim}
The \texttt{screen.navigation.category.order} and
\texttt{screen.navigation.entry.order} determine where in the
navigation the items appear. Higher is first in the navigation.
In the \texttt{service} declaration, declare it as defining a
\texttt{ScreenNavigationCategory}, \texttt{ScreenNavigationEntry}, or
both.
\item
For the class body, insert this code:
\begin{verbatim}
@Override
public String getCategoryKey() {
return "custom-screen";
}
@Override
public String getEntryKey() {
return "custom-screen";
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "custom-screen");
}
@Override
public String getScreenNavigationKey() {
return AssetCategoriesConstants.CATEGORY_KEY_GENERAL;
}
@Override
public void render(HttpServletRequest request, HttpServletResponse response)
throws IOException {
_jspRenderer.renderJSP(request, response, "/category/custom-screen.jsp");
}
@Reference
private JSPRenderer _jspRenderer;
\end{verbatim}
\item
Create a \texttt{custom-screen.jsp} in the
\texttt{/resources/META-INF/resources/category/} folder.
\item
At the top of your JSP class, insert the following scriptlet to use
the Screen Navigation UI:
\begin{verbatim}
<%
String redirect = ParamUtil.getString(request, "redirect", assetCategoriesDisplayContext.getEditCategoryRedirect());
long categoryId = ParamUtil.getLong(request, "categoryId");
AssetCategory category = AssetCategoryLocalServiceUtil.fetchCategory(categoryId);
long parentCategoryId = BeanParamUtil.getLong(category, request, "parentCategoryId");
long vocabularyId = ParamUtil.getLong(request, "vocabularyId");
portletDisplay.setShowBackIcon(true);
portletDisplay.setURLBack(redirect);
renderResponse.setTitle(((category == null) ? LanguageUtil.get(request, "add-new-category") : category.getTitle(locale)));
%>
\end{verbatim}
\item
Below that, insert the following tag:
\begin{verbatim}
\end{verbatim}
\item
For the rest of the JSP, create your custom screen.
\end{enumerate}
Now you can use that pattern to create additional screens for whatever
you need.
\chapter{Developing a Fragment
Renderer}\label{developing-a-fragment-renderer}
When creating Fragments through Liferay DXP's provided UI, you're given
three front-end languages to leverage: CSS, HTML, and JavaScript.
Although you can harness a lot of power with these languages alone, they
do not provide an easy way to retrieve and process information from the
database or third party systems. A common solution for this issue is
creating a full-fledged portlet to complete common back-end necessities,
but this is sometimes overkill for what you need.
For a lightweight alternative, you can develop a \emph{Fragment
Renderer} to use Liferay's provided Java APIs for back-end tasks related
to your Fragment. To do this, you must
\hyperref[implementing-the-fragmentrenderer-interface]{implement the
\texttt{FragmentRenderer} interface}.
Optionally, you can
\begin{itemize}
\item
\hyperref[leveraging-the-fragmentrenderercontext]{Leverage the
\texttt{FragmentRendererContext}}.
\item
\hyperref[rendering-jsps]{Use JSPs for your Fragment's display}.
\item
\hyperref[choosing-when-to-display-a-component]{Choose when to display
the component}.
\item
\hyperref[translating-the-collection-language-key]{Translate the
Collection language key}.
\end{itemize}
You'll explore each step next.
\section{Implementing the FragmentRenderer
Interface}\label{implementing-the-fragmentrenderer-interface}
The
\href{https://docs.liferay.com/dxp/apps/fragment/latest/javadocs/com/liferay/fragment/renderer/FragmentRenderer.html}{\texttt{FragmentRenderer}}
interface requires the implementation of two methods:
\texttt{getCollectionKey}: returns the unique key for the component's
Collection. Define this key in several components to group them under a
collapsible panel in the Page Editor.
\texttt{getLabel}: provides the Fragment name.
The remaining methods are optional, but can be useful in many scenarios:
\texttt{getImagePreviewURL}: returns the URL for previewing the
Fragment's image.
\texttt{getKey}: returns the Fragment's key.
\texttt{getType}: returns the Fragment's type. Type values include
\texttt{FragmentConstants.TYPE\_COMPONENT} and
\texttt{FragmentConstants.TYPE\_SECTION}.
\texttt{isSelectable}: defines whether page authors can select the
Fragment Renderer. You'll learn more about this in the
\hyperref[choosing-when-to-display-a-component]{Choosing When to Display
a Component} section.
\texttt{render} (highly recommended): defines how to render the Fragment
Renderer (e.g., JSP or FreeMarker). You can leverage the
\texttt{FragmentRendererContext} in this method to facilitate the
rendering process.
Next, you'll learn about leveraging the
\texttt{FragmentRendererContext}.
\section{Leveraging the
FragmentRendererContext}\label{leveraging-the-fragmentrenderercontext}
The \texttt{render} method receives a read-only instance of the
interface
\href{https://docs.liferay.com/dxp/apps/fragment/latest/javadocs/com/liferay/fragment/renderer/FragmentRendererContext.html}{\texttt{FragmentRendererContext}}.
This provides information about the context in which the Fragment is
being rendered. The fields of information that are accessible through it
include
\textbf{Fragment Entry Link}: The specific instance of the Fragment
being rendered. This information can be used to identify the specific
site or page to which the Fragment was added, when it was added, the
user who added it, etc.
\textbf{Locale}: The current locale to be used for any multi-locale
text.
\textbf{Mode}: There are three available modes:
\begin{itemize}
\tightlist
\item
\textbf{VIEW}: The component is being rendered within a page being
viewed (not edited).
\item
\textbf{ASSET\_DISPLAY\_PAGE}: The component is being edited on a
Display Page.
\item
\textbf{EDIT}: The component is being edited on a Content Page.
\end{itemize}
There are other fields which should only be necessary for advanced use
cases:
\textbf{Preview Class PK}: If the Fragment supports displaying content,
this field supports previewing an \emph{In progress} version of the
content before it's ready to publish. In this case, the \texttt{render}
method returns the content's primary key.
\textbf{Preview Type}: Represents the preview type you want to show. The
accepted values include
\begin{itemize}
\tightlist
\item
\textbf{TYPE\_LATEST\_APPROVED}: The latest approved version of the
content.
\item
\textbf{TYPE\_LATEST}: The latest version of the content.
\end{itemize}
\textbf{Field Values}: Fragments can have editable elements through
\texttt{\textless{}lfr-editable\textgreater{}} tags; this also applies
to those created with \texttt{FragmentRenderer}. The
\texttt{getFieldValuesOptional()} method retrieves the field values the
user may have introduced in them. This only applies in the context of a
Display Page with the values of the mapped structure.
\textbf{Segment Experience IDs}: A list of identifiers for experiences
that have been configured for the current page.
\section{Rendering JSPs}\label{rendering-jsps}
Usually you'll want to avoid writing HTML in your Java code.
Fortunately, you can use the \texttt{render} method to use any
templating mechanism of your choice. JSP integration is provided
out-of-the box.
For example, rendering a JSP for your Fragment Renderer would look like
this:
\begin{verbatim}
@Override
public void render(
FragmentRendererContext fragmentRendererContext,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws IOException {
httpServletRequest.setAttribute(
"fragmentRendererContext", fragmentRendererContext);
_jspRenderer.renderJSP(
httpServletRequest, httpServletResponse, "/my-component.jsp");
}
@Reference
private JSPRenderer _jspRenderer;
@Reference(
target = "(osgi.web.symbolicname=com.liferay.fragment.renderer.docs)",
unbind = "-"
)
private ServletContext _servletContext;
\end{verbatim}
This sets the
\hyperref[leveraging-the-fragmentrenderercontext]{\texttt{FragmentRendererContext}}
in the HTTP servlet request, which is then used to render the included
JSP file (e.g., \texttt{my-component.jsp}).
To leverage JSPs, you must specify the servlet context for the JSP
files. Since your Fragment Renderer is an OSGi module, your
\texttt{bnd.bnd} file must define a web context path:
\begin{verbatim}
Bundle-SymbolicName: com.liferay.fragment.renderer.docs
Web-ContextPath: /my-fragment-renderer
\end{verbatim}
Then you must reference the Servlet context using the symbolic name of
your module, as was shown above:
\begin{verbatim}
@Reference(
target = "(osgi.web.symbolicname=com.liferay.fragment.renderer.docs)",
unbind = "-"
)
private ServletContext _servletContext;
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} To use the JSP Renderer, your module must set the
\texttt{com.liferay.frontend.taglib} dependency in its build file.
\noindent\hrulefill
Next, you'll learn about controlling when your Fragment Renderer is
displayed.
\section{Choosing When to Display a
Component}\label{choosing-when-to-display-a-component}
Sometimes offering Fragment components only makes sense in specific
cases. You can implement the \texttt{isSelectable(...)} method to
specify under which conditions the Fragment Renderer is available to
page authors.
For example, if you wanted to make your Fragment Renderer only available
in Display Pages, you could implement the \texttt{isSelectable} method
like this:
\begin{verbatim}
@Override
public boolean isSelectable(HttpServletRequest httpServletRequest) {
Layout layout = (Layout)httpServletRequest.getAttribute(WebKeys.LAYOUT);
if (Objects.equals(
layout.getType(), LayoutConstants.TYPE_ASSET_DISPLAY)) {
return true;
}
return false;
}
\end{verbatim}
This determines the Fragment Renderer's page type and returns
\texttt{true} when the page type is a Display Page or \texttt{false} if
it's not.
\section{Translating the Collection Language
Key}\label{translating-the-collection-language-key}
When setting your Fragment Renderer's collection name via the
\texttt{getCollectionKey} method, you should specify it as a language
key and then define it in a resource bundle.
For example, a \texttt{getCollectionKey} method could look like this:
\begin{verbatim}
@Override
public String getCollectionKey() {
return "sample-components";
}
\end{verbatim}
To specify \texttt{sample-components} in a resource bundle, create the
\texttt{src/main/resources/content/Language.properties} file within the
Fragment Renderer module and define it using the language key
\texttt{fragment.collection.label.\{collection-key\}}. For example,
\begin{verbatim}
fragment.collection.label.sample-components=Sample Components
\end{verbatim}
To learn more about resource bundles, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization}
section.
Next, you'll step through creating a Fragment Renderer.
\chapter{Creating a Fragment
Renderer}\label{creating-a-fragment-renderer}
Creating a Fragment Renderer lets you call Liferay's provided Java APIs
for back-end tasks related to your Fragment. In this article, you'll
create a sample Fragment Renderer that displays values stored in the
current Liferay DXP instance's database.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a default module project} in your development environment.
\item
Create a unique package name in the module's \texttt{src} directory
and create a new Java class in that package. To follow naming
conventions, give your class a unique name followed by
\texttt{FragmentRenderer} (e.g.,
\texttt{ShowContextFragmentRenderer}).
\item
Configure your new class to implement the
\href{https://docs.liferay.com/dxp/apps/fragment/latest/javadocs/com/liferay/fragment/renderer/FragmentRenderer.html}{\texttt{FragmentRenderer}}
interface:
\begin{verbatim}
public class ShowContextFragmentRenderer implements FragmentRenderer {
}
\end{verbatim}
\item
Insert the following \texttt{@Component} annotation above the class
declaration:
\begin{verbatim}
@Component(service = FragmentRenderer.class)
\end{verbatim}
This sets the OSGi service type to \texttt{FragmentRenderer}.
\item
Implement the two required \texttt{FragmentRenderer} methods:
\begin{verbatim}
@Override
public String getCollectionKey() {
return "sample-components";
}
@Override
public String getLabel(Locale locale) {
return "Show Context Component";
}
\end{verbatim}
The \texttt{getCollectionKey()} method returns a language key, which
you'll define later. The name displayed for this Fragment is defined
as \emph{Show Context Component}.
\begin{figure}
\centering
\includegraphics{./images/show-context-fragment-renderer.png}
\caption{The new Fragment Renderer appears in its defined component
collection.}
\end{figure}
\item
Implement the \texttt{render} method:
\begin{verbatim}
@Override
public void render(
FragmentRendererContext fragmentRendererContext,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws IOException {
PrintWriter printWriter = httpServletResponse.getWriter();
printWriter.write("Context ");
printWriter.write("");
FragmentEntryLink fragmentEntryLink =
fragmentRendererContext.getFragmentEntryLink();
printWriter.write("Added by: " + fragmentEntryLink.getUserName());
printWriter.write(" Added in: " + fragmentEntryLink.getCreateDate());
printWriter.write(" Locale: " + fragmentRendererContext.getLocale());
printWriter.write(" Mode: " + fragmentRendererContext.getMode());
printWriter.write(" PreviewClassPK: " + fragmentRendererContext.getPreviewClassPK());
printWriter.write(" PreviewType: " + fragmentRendererContext.getPreviewType());
printWriter.write(" Segment experiences: " + StringUtil.merge(fragmentRendererContext.getSegmentsExperienceIds(), ", "));
printWriter.write(" ");
}
\end{verbatim}
This method leverages the
\href{/docs/7-2/frameworks/-/knowledge_base/f/developing-a-fragment-renderer\#leveraging-the-fragmentrenderercontext}{\texttt{FragmentRendererContext}},
which provides the Fragment's context information stored in the
database. This information is displayed in the Fragment Renderer when
it's placed on a page.
\begin{figure}
\centering
\includegraphics{./images/show-context-fragment-renderer-page.png}
\caption{When adding the new Fragment Renderer to a page, the context
information is displayed.}
\end{figure}
\item
Define the language key \texttt{sample-components} that you used in
the \texttt{getCollectionKey()} method. To do this, create the
\texttt{src/main/resources/content/Language.properties} file and add
the following language key:
\begin{verbatim}
fragment.collection.label.sample-components=Sample Components
\end{verbatim}
\item
Provide the appropriate dependencies to compile your Fragment Renderer
project. For example, the following dependencies are defined for the
Show Context Component Fragment Renderer sample (Gradle build)
deployed to Liferay Portal 7.2 GA1:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "4.13.0"
compileOnly group: "com.liferay", name: "com.liferay.fragment.api", version: "2.7.2"
compileOnly group: "com.liferay", name: "com.liferay.fragment.service", version: "2.0.10"
compileOnly group: "com.liferay", name: "com.liferay.frontend.taglib", version: "4.0.15"
compileOnly group: "com.liferay", name: "com.liferay.petra.string", version: "3.0.0"
compileOnly group: "javax.portlet", name: "portlet-api", version: "3.0.0"
compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
compileOnly group: "jstl", name: "jstl", version: "1.2"
compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
}
\end{verbatim}
To stay in sync with the appropriate versions of your project's
dependencies, consider using the
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{Target
Platform} framework.
\end{enumerate}
That's it! You can compile the sample \emph{Show Context Component}
Fragment Renderer and
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploy
it}! It'll be available to add for a Fragment-enabled page under the
\emph{Sample Components} collection.
\chapter{Web Services}\label{web-services}
It's important for apps on different machines to communicate. To enable
this, an app can expose APIs so remote components (other apps or
devices) can access the app's features. For example, one service could
have a client app presenting information to users, a server app
processing data in B2B setting, and an IoT device requesting data to do
its work. Exposing web APIs lets external applications or devices
communicate with yours.
Because Liferay DXP contains so many apps and features, it's prudent for
Liferay to let developers access those apps and features from external
apps and devices by exposing their APIs. Additionally, Liferay's
development platform makes it easy to extend them and create new ones.
There are three different approaches for clients to connect to Liferay
DXP's web APIs:
\textbf{Headless REST APIs:} You can consume RESTful web services
independent of Liferay DXP's front end (hence \emph{headless}). These
APIs conform to the
\href{https://swagger.io/docs/specification/about/}{OpenAPI}
specification. This is the modern, preferred way to work with web
services in Liferay DXP.
\textbf{GraphQL:} All the power of doing multiple queries in a unique
request following
\href{https://graphql.github.io/graphql-spec/June2018/}{GraphQL
specification}.
\textbf{Plain Web/REST Services:} This is the old way to build and
consume web services in Liferay DXP, but is still supported.
You can also create your own Headless REST and GraphQL APIs through the
\textbf{REST builder}.
\chapter{Headless REST APIs}\label{headless-rest-apis}
Liferay DXP's headless REST APIs follow the
\href{https://swagger.io/docs/specification/about/}{OpenAPI}
specification and let your apps consume RESTful web services. What's
more, you can consume these APIs without being tied to Liferay DXP's UI
(hence the term \emph{headless}). This gives you a great deal of freedom
when designing and developing your apps.
The articles in this section show you how to navigate and consume
Liferay DXP's headless REST APIs. But first, you'll learn the design
approach for these APIs.
\section{OpenAPI}\label{openapi}
\href{https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md}{OpenAPI}
(originally called Swagger) is a Linux Foundation project specification
that defines machine-readable files that describe REST APIs and how to
consume them.
OpenAPI has become a widely adopted standard for defining REST APIs and
is supported by major players in the API ecosystem such as Google,
Amazon, and Microsoft. As a spec, it is language-agnostic, and many
libraries implement it or provide code generation to help validate,
consume, or produce APIs.
Liferay DXP leverages existing knowledge of OpenAPI to define, create
and consume REST APIs.
\section{API Vocabulary}\label{api-vocabulary}
When defining an API, the developer must decide how to expose the
representation of its resources. This determines its ease of use and how
it can evolve. Traditionally, there are two approaches:
\textbf{Contract Last:} The code is written first and features are
exposed as web or REST services. This approach is typically easier for
developers, as they must only implement and expose the business logic.
Service Builder is an example of this.
\textbf{Contract First:} The structure for client-server messages is
written before the code that implements the services. Such messages are
defined independent of the code. This avoids tight coupling and is less
likely to break clients as APIs evolve.
Liferay DXP's headless web APIs use a mixture of both approaches. An
OpenAPI profile uses a contract first approach by defining the paths and
schemas before writing any code. It then generates an API automatically
based on that profile, using the contract-last characteristic of code
generation (like Service Builder). This allows fast development for
developers.
This mixed approach delivers the best of both worlds, allowing a step of
conscious API design and then simplifying the developer experience by
exposing only the business logic to implement.
When writing the OpenAPI profile, the main focus should be on defining
how client-server messages represent the APIs' resources. In other
words, the APIs' schemas are defined first and the attributes,
resources, and operations are named to clearly define what they
represent and how they should be used.
\chapter{Get Started: Find the API}\label{get-started-find-the-api}
To begin consuming web services, you must first know where they are
(e.g., a service catalog), what operations you can invoke, and how to
invoke them. Because Liferay DXP's headless REST APIs leverage
\href{https://en.wikipedia.org/wiki/OpenAPI_Specification}{OpenAPI}
(originally known as Swagger), you don't need a service catalog. You
only need to know the OpenAPI profile from which to discover the rest of
the API.
Liferay DXP's headless APIs are available in SwaggerHub at
\href{https://app.swaggerhub.com/organizations/liferayinc}{\texttt{https://app.swaggerhub.com/organizations/liferayinc}}.
Each API has its own URL in SwaggerHub. For example, you can access the
delivery API definition at
\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0}{\texttt{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0}}.
Each OpenAPI profile is also deployed dynamically in your portal
instance under this schema:
\begin{verbatim}
http://[host]:[port]/o/[insert-headless-api]/[version]/openapi.yaml
\end{verbatim}
For example, if you're running Liferay DXP locally on port
\texttt{8080}, the home URL for discovering the headless delivery API
is:
\begin{verbatim}
http://localhost:8080/o/headless-delivery/v1.0/openapi.yaml
\end{verbatim}
You must be logged in to access this URL, or use basic authentication
and a browser or other tool like
\href{https://www.getpostman.com}{Postman},
\href{https://install.advancedrestclient.com/install}{Advanced REST
Client}, or even the \texttt{curl} command in your system console.
For simplicity, the examples in this documentation use the \texttt{curl}
command and send requests to a Liferay DXP instance running locally on
port \texttt{8080}.
Run this \texttt{curl} command to access the home URL:
\begin{verbatim}
curl http://localhost:8080/o/headless-delivery/v1.0/openapi.yaml -u test@example.com:test
\end{verbatim}
You should get a response like this:
\begin{verbatim}
openapi: 3.0.1
info:
title: Headless Delivery
version: v1.0
paths:
/v1.0/blog-posting-images/{blogPostingImageId}:
get:
tags:
- BlogPostingImage
operationId: getBlogPostingImage
parameters:
- name: blogPostingImageId
in: path
required: true
schema:
type: integer
format: int64
responses:
default:
description: default response
content:
application/json:
schema:
$ref: '#/components/schemas/BlogPostingImage'
(...)
\end{verbatim}
This response follows the OpenAPI version 3.0 syntax to specify the
endpoints (URLs) of the API and schemas returned. You can also open the
OpenAPI profile in an OpenAPI editor like the
\href{https://editor.swagger.io}{Swagger Editor}. You can use this
editor to inspect the documentation and parameters and make requests to
the API.
There are also many other tools that support OpenAPI, such as client
generators, validators, parsers, and more. See
\href{https://openapi.tools/}{OpenAPI.Tools} for a comprehensive list.
Leveraging OpenAPI provides standards support, extensive
\href{https://swagger.io/docs/}{documentation}, and industry-wide
conventions.
\section{Related Topics}\label{related-topics-124}
\href{/docs/7-2/frameworks/-/knowledge_base/f/get-started-invoke-a-service}{Get
Started: Invoke a Service}
\chapter{How To Invoke a Service}\label{how-to-invoke-a-service}
Once you know which API you want to call via the
\href{/docs/7-2/frameworks/-/knowledge_base/f/get-started-discover-the-api}{OpenAPI
profile}, you can send a request to that resource's URL. For example,
suppose you want to retrieve all the blog entries from a Site. If you
consult the OpenAPI profile for Liferay DXP's delivery API, you can find
this endpoint:
\begin{verbatim}
"/sites/{siteId}/blog-postings":
get:
operationId: getSiteBlogPostingsPage
parameters:
- in: path
name: siteId
required: true
schema:
format: int64
type: integer
- in: query
name: filter
schema:
type: string
- in: query
name: page
schema:
type: integer
- in: query
name: pageSize
schema:
type: integer
- in: query
name: search
schema:
type: string
- in: query
name: sort
schema:
type: string
responses:
200:
content:
application/json:
schema:
items:
$ref: "#/components/schemas/BlogPosting"
type: array
description: ""
tags: ["BlogPosting"]
\end{verbatim}
The only required parameter is \texttt{siteId}, the ID of the blog
postings' Site. Internally, the \texttt{siteId} is a \texttt{groupId}
that you can retrieve from the database, a URL, or Liferay DXP's UI via
the Site Administration menu. The following GET request gets the site's
blog postings by providing the site ID (\texttt{20124}) in the URL:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20124/blog-postings/" -u 'test@example.com:test'
\end{verbatim}
If you send such a request to a site that contains some blog entries,
the response should look like this:
\begin{verbatim}
{
"items": [
{
"alternativeHeadline": "The power of OpenAPI & Liferay",
"articleBody": "We are happy to announce...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"dateModified": "2019-04-22T07:04:51Z",
"datePublished": "2019-04-22T07:02:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "new-headless-apis",
"headline": "New Headless APIs",
"id": 59301,
"numberOfComments": 0,
"siteId": 20124
}
],
"lastPage": 1,
"page": 1,
"pageSize": 20,
"totalCount": 1
}
\end{verbatim}
This response is a JSON object with information about the collection of
blogs. The response's attributes contain information about the resource
(blogs, in this case). Also note that the results are paginated. The
\texttt{*page*} attributes refer to pages of results. Here's a
description of some common attributes:
\texttt{id}: Each item has an ID. You can use the ID to retrieve more
information about that item. For example, there are two \texttt{id}
attributes in the above response: one for the blog posting
(\texttt{59301}) and one for the blog post's creator (\texttt{20130}).
\texttt{lastPage}: The page number of the final page of results. The
above response only contains a single page, so its last page is
\texttt{1}.
\texttt{page}: The page number of the current page. The page in the
above response is \texttt{1}.
\texttt{pageSize}: The possible number of this resource's items to be
included in a single page. In the above response this is \texttt{20}.
\texttt{totalCount}: The total number of this resource's existing items
(independent of pagination). The above response lists the total number
of blog postings (\texttt{1}) in a Site.
To get information on a specific blog posting, send a GET request to the
\texttt{blogPostingId} resource's URL with the blog posting's ID
(\texttt{/blog-postings/\{blogPostingId\}}). For example, the URL for
such a request to the blog posting in the above response is
\texttt{/blog-postings/59301}. Here's an example response:
\begin{verbatim}
{
"alternativeHeadline": "The power of OpenAPI & Liferay",
"articleBody": "We are happy to announce...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"dateModified": "2019-04-22T07:04:51Z",
"datePublished": "2019-04-22T07:02:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "new-headless-apis",
"headline": "New Headless APIs",
"id": 59301,
"numberOfComments": 0,
"siteId": 20124
}
\end{verbatim}
Although this response is JSON, the API's consumer can select other
formats to use (like XML). For more information, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}.
\section{Related Topics}\label{related-topics-125}
\href{/docs/7-2/frameworks/-/knowledge_base/f/get-started-discover-the-api}{Get
Started: Discover the API}
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}
\chapter{Making Authenticated
Requests}\label{making-authenticated-requests}
To make an authenticated request, you must authenticate as a specific
User.
There are three authentication mechanisms available when invoking web
APIs:
\textbf{Basic Authentication:} Sends the user credentials as an encoded
user name and password pair. This is the simplest authentication
protocol (available since HTTP/1.0).
\textbf{OAuth 2.0:} In 7.0, you can use OAuth 2.0 for authentication.
See the \href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0}{OAuth 2.0
documentation} for more information.
\textbf{Cookie/Session authentication:} From inside the portal you can
make direct requests to the APIs by sending the session token.
First, you'll learn how send requests with basic authentication.
\section{Basic Authentication}\label{basic-authentication}
Basic authentication requires that you send an HTTP
\texttt{Authorization} header containing the encoded user name and
password. You must first get that encoded value. To do so, you can use
\texttt{openssl} or a \texttt{Base64} encoder. Either way, you must
encode the \texttt{user:password} string. Here's an example of the
\texttt{openssl} command for encoding the \texttt{user:password} string
for a user \texttt{test@example.com} with the password \texttt{Liferay}:
\begin{verbatim}
openssl base64 <<< test@example.com:Liferay
\end{verbatim}
This returns the encoded value:
\begin{verbatim}
dGVzdEBleGFtcGxlLmNvbTpMaWZlcmF5Cg==
\end{verbatim}
If you don't have \texttt{openssl} installed, try the \texttt{base64}
command:
\begin{verbatim}
base64 <<< test@example.com:Liferay
\end{verbatim}
\noindent\hrulefill
\textbf{Warning:} Encoding a string as shown here does not encrypt the
resulting string. An encoded string can easily be decoded by executing
\texttt{base64\ \textless{}\textless{}\textless{}\ the-encoded-string},
which returns the original string.
Anyone listening to your request could therefore decode the
\texttt{Authorization} header and reveal your user name and password. To
prevent this, ensure that all communication is made through HTTPS, which
encrypts the entire message (including headers).
\noindent\hrulefill
Use the encoded value for the HTTP Authorization header when sending the
request:
\begin{verbatim}
curl -H "Authorization: Basic dGVzdEBleGFtcGxlLmNvbTpMaWZlcmF5Cg==" http://localhost:8080/o/headless-delivery/v1.0/sites/{siteId}/blog-postings/
\end{verbatim}
The response contains data instead of the 403 error that an
unauthenticated request receives. For more information on the response's
structure, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}.
\begin{verbatim}
{
"items": [
{
"alternativeHeadline": "The power of OpenAPI & Liferay",
"articleBody": "We are happy to announce...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"dateModified": "2019-04-22T07:04:51Z",
"datePublished": "2019-04-22T07:02:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "new-headless-apis",
"headline": "New Headless APIs",
"id": 59301,
"numberOfComments": 0,
"siteId": 20124
},
{
"alternativeHeadline": "How to work with OAuth",
"articleBody": "To configure OAuth...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T09:35:09Z",
"dateModified": "2019-04-22T09:35:09Z",
"datePublished": "2019-04-22T09:34:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "authenticated-requests",
"headline": "Authenticated requests",
"id": 59309,
"numberOfComments": 0,
"siteId": 20124
}
],
"lastPage": 1,
"page": 1,
"pageSize": 20,
"totalCount": 2
}
\end{verbatim}
\section{OAuth 2.0 Authorization}\label{oauth-2.0-authorization}
7.0 supports authorization via OAuth 2.0, which is a token-based
authorization mechanism. For more details, see
\href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0}{Liferay DXP's OAuth
2.0 documentation}. The following sections show you how to use OAuth 2.0
to authenticate web API requests.
\section{Obtaining the OAuth 2.0
Token}\label{obtaining-the-oauth-2.0-token}
Before using OAuth 2.0 to invoke a web API, you must register your
application (your web API's consumer) as an authorized OAuth client. To
do this, follow the instructions in the
\href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0\#creating-an-application}{Creating
an Application} section of the OAuth 2.0 documentation. When creating
the application, fill in the form as follows:
\textbf{Application Name:} Your application's name.
\textbf{Client Profile:} Headless Server.
\textbf{Allowed Authorization Types:} Check \emph{Client Credentials}.
After clicking \emph{Save} to finish creating the application, write
down the Client ID and Client Secret values that appear at the top of
the form.
Next, you must get an OAuth 2.0 access token. To do this, see the
tutorial
\href{/docs/7-2/deploy/-/knowledge_base/d/authorizing-account-access-with-oauth2}{Authorizing
Account Access with OAuth 2}.
\section{Invoking the Service with an OAuth 2.0
Token}\label{invoking-the-service-with-an-oauth-2.0-token}
Once you have a valid OAuth 2.0 token, include it in the request's
\texttt{Authorization} header, specifying that the authentication type
is a \href{https://tools.ietf.org/html/rfc6750}{bearer token}. For
example:
\begin{verbatim}
curl -H "Authorization: Bearer d5571ff781dc555415c478872f0755c773fa159" http://localhost:8080/o/headless-delivery/v1.0/sites/{siteId}/blog-postings/
\end{verbatim}
The response contains the resources that the authenticated user has
permission to access, just like the response from Basic authentication.
\section{Using Cookie Authentication or Making Requests from the
UI}\label{using-cookie-authentication-or-making-requests-from-the-ui}
You can call the REST APIs using the existing session from outside
Liferay DXP by passing the session identifier (the cookie reference) and
the Liferay Auth Token (a Cross-Site Request Forgery---CSRF---token).
To do a request from outside Liferay DXP you must provide the
\texttt{Cookie} identifier in the header. In CURL, pass the \texttt{-H}
parameter:
\begin{verbatim}
-H 'Cookie: JSESSIONID=27D7C95648D7CDBE3347601FC4543F5D'
\end{verbatim}
You must also provide the CSRF token by passing it in the
\texttt{p\_auth} query parameter, or by adding the URL to the whitelist
of CSRF allowed URLs or disabling CSRF checks altogether with the
\texttt{auth.verifier.auth.verifier.PortalSessionAuthVerifier.check.csrf.token}
property (application level).
Here's a sample CURL request with the cookie and CSRF token:
\begin{verbatim}
curl -H 'Cookie: JSESSIONID=27D7C95648D7CDBE3347601FC4543F5D' http://localhost:8080/o/headless-delivery/v1.0/sites/{siteId}/blog-postings/?p_auth=O4dCU1Mj
\end{verbatim}
To do a cookie request from inside Liferay DXP, from JavaScript code or
a Java method, the session identifier is not needed and you must only
provide the CRSF token or add the API to the whitelist of CSRF allowed
URLs.
\section{Making Unauthenticated
Requests}\label{making-unauthenticated-requests}
Unauthenticated requests are disabled by default in Liferay DXP's
headless REST APIs. You can, however, enable them manually by defining
an exception in the Service Access Policy to allow unauthenticated
requests.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to Control Panel → Configuration → Service Access Policy.
\item
Add a new Service Access Policy.
\item
Enable both \emph{Enabled} and \emph{Default} options.
\item
Use
\texttt{com.liferay.headless.delivery.internal.resource.v1\_0.OpenAPIResourceImpl}
for the Service Class and \texttt{getOpenAPI} for the Method Name (or
the method/class you want to expose).
\item
Test the APIs by making a request to an OpenAPI profile URL:
\end{enumerate}
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/openapi.yaml"
\end{verbatim}
You should get the OpenAPI profile for the API you sent the request to.
\section{Cross-Origin Resource Sharing
(CORS)}\label{cross-origin-resource-sharing-cors}
Modern web browsers block access to content from domains other than the
one currently being visited. For example, browsers block fetch/ajax
requests from a local JavaScript application (being executed in
localhost:4000) that tries to access a Tomcat server (running in
localhost:8080).
Cross Origin Resource Sharing allows the configuration of safe resource
sharing between sites. A web application using APIs can only request
endpoints that have the same origin/domain unless some special CORS
headers are set that explicitly allow querying from different domains.
For development purposes, it's common to enable CORS headers to allow
scripts to call APIs served by a different server.
\begin{figure}
\centering
\includegraphics{./images/cors.png}
\caption{Configure Cross-Origin Resource Sharing in Liferay}
\end{figure}
Follow these instructions to configure
\href{/docs/7-2/deploy/-/knowledge_base/d/configuring-cors}{Cross-Origin
Resource Sharing (CORS)} in Liferay DXP.
\section{Related Topics}\label{related-topics-126}
\href{/docs/7-2/frameworks/-/knowledge_base/f/get-started-invoke-a-service}{Get
Started: Invoke a Service}
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}
\chapter{Working with Collections of
Data}\label{working-with-collections-of-data}
Collection resources are common in Liferay DXP web APIs. If you followed
along with the previous examples that sent requests to the portal's
\texttt{blog-postings} resource URL, you've already seen collections in
action: the \texttt{BlogPosting} resource is a collection.
Here, you'll learn more detailed information about working with
collection resources. But first you should learn about how collections
are returned in pages.
\section{Pagination}\label{pagination}
A small collection can be transmitted in a single response without
difficulty. Transmitting a large collection all at once, however, can
consume too much bandwidth, time, and memory. It can also overwhelm the
user with too much data. It's therefore best to get and display the
elements of a large collection in discrete chunks, or pages.
Liferay DXP's headless REST APIs return paginated collections by
default. The following attributes in the responses also contain the
information needed to navigate between those pages:
\texttt{totalCount}: The total number of this resource's items.
\texttt{pageSize}: The number of this resource's items to be included in
this response.
\texttt{page}: The current page's number.
\texttt{lastPage}: The last page's number.
\texttt{items}: The collection elements present in this page. Each
element also contains the data of the object it represents, so there's
no need for additional requests for individual elements.
\texttt{id}: Each item's identifier. You can use this, if necessary, to
get more information on a specific item.
For examples of working with collection pages, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/pagination}{Pagination}.
\chapter{Getting Collections}\label{getting-collections}
Requests for collection resources are the same as those for
non-collection resources. For example, an
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-authenticated-requests}{authenticated
request} to the \texttt{UserAccount} endpoint returns a collection
containing the portal's users. When sending this request, use the
credentials of an administrative user who has permission to view other
portal users:
\begin{verbatim}
curl "http://localhost:8080/o/headless-admin-user/v1.0/user-accounts" -u 'test@example.com:test'
\end{verbatim}
The response (below) has two main parts:
\begin{itemize}
\item
The list of collection elements, inside the \texttt{items} attribute.
This example contains data on two users: an administrator (Test), and
a user named Javier Gamarra.
\item
A set of metadata about the collection. This is the rest of the data
in the response. This lets clients know how to use the collection.
\end{itemize}
This response is in JSON, which is the default response format for web
APIs in Liferay DXP. For information on specifying other response
formats, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}.
\begin{verbatim}
{
"items": [
{
"alternateName": "test",
"birthDate": "1970-01-01T00:00:00Z",
"contactInformation": {},
"dashboardURL": "/user/test",
"dateCreated": "2019-04-17T20:37:19Z",
"dateModified": "2019-04-22T09:56:35Z",
"emailAddress": "test@example.com",
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test",
...
},
{
"alternateName": "nhpatt",
"birthDate": "1970-01-01T00:00:00Z",
"contactInformation": {},
"dateCreated": "2019-04-22T10:38:36Z",
"dateModified": "2019-04-22T10:38:37Z",
"emailAddress": "nhpatt@gmail.com",
"familyName": "Gamarra",
"givenName": "Javier",
"id": 59347,
"name": "Javier Gamarra",
...
}
],
"lastPage": 1,
"page": 1,
"pageSize": 20,
"totalCount": 2
}
\end{verbatim}
\section{Related Topics}\label{related-topics-127}
\href{/docs/7-2/frameworks/-/knowledge_base/f/pagination}{Pagination}
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-authenticated-requests}{Making
Authenticated Requests}
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}
\chapter{Pagination}\label{pagination-1}
Collection resources are returned in pages of information.
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data} explains this in more detail. Here, you'll
learn how to work with collection pages.
For example, suppose that there are 123 users your portal and you want
to get information on them. To do this, send an
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-authenticated-requests}{authenticated
request} to the UserAccount URL:
\begin{verbatim}
curl "http://localhost:8080/o/headless-admin-user/v1.0/user-accounts" -u 'test@example.com:test'
\end{verbatim}
The response contains the first 30 users and IDs for navigating the rest
of the collection. Note that most of the contents of the \texttt{items}
attribute, which contains the users, are omitted here so you can focus
on the metadata for navigating the collection:
\begin{verbatim}
{
"items": [
{
"id": 20130,
...
},
{
"id": 59347,
...
}
],
"lastPage": 5,
"page": 1,
"pageSize": 30,
"totalCount": 123
}
\end{verbatim}
The attributes \texttt{page} and \texttt{pageSize} allow client
applications to navigate through the results. For example, such a client
could send a request for a specific page. This example gets the second
page (\texttt{?page=2}) of documents that exist on the site with the ID
\texttt{20124}:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20124/documents?page=2" -u 'test@example.com:test'
\end{verbatim}
Similarly, you can customize the number of elements per page via the
optional parameter \texttt{pageSize} (e.g., \texttt{?pageSize=20}).
\section{Related Topics}\label{related-topics-128}
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-authenticated-requests}{Making
Authenticated Requests}
\chapter{Navigating from a Collection to its
Elements}\label{navigating-from-a-collection-to-its-elements}
When you
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-collections}{get a
collection}, you can use the response to get an element of that
collection. Follow these steps to do so:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Get a collection. This example gets a list of users by sending
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-authenticated-requests}{an
authenticated request} to the \texttt{user-accounts} collection:
\begin{verbatim}
curl "http://localhost:8080/o/headless-admin-user/v1.0/user-accounts" -u 'test@example.com:test'
\end{verbatim}
Recall from
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-collections}{Getting
Collections} that the response's \texttt{items} attribute contains the
collection elements. In this case, the collection contains two users:
Test Test and Javier Gamarra:
\texttt{json\ \{\ \ \ \ \ "totalItems":\ 2,\ \ \ \ \ "numberOfItems":\ 2,\ \ \ \ \ "view":\ \{\ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ "items":\ {[}\ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "alternateName":\ "test",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "birthDate":\ "1970-01-01T00:00:00Z",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "contactInformation":\ \{\},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "dashboardURL":\ "/user/test",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "dateCreated":\ "2019-04-17T20:37:19Z",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "dateModified":\ "2019-04-22T09:56:35Z",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "emailAddress":\ "test@example.com",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "familyName":\ "Test",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "givenName":\ "Test",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20130,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Test\ Test",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "profileURL":\ "/web/test",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "roleBriefs":\ {[}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20108,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Administrator"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20111,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Power\ User"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20112,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "User"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ {]},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "siteBriefs":\ {[}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20128,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Global"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20124,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Guest"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ {]}\ \ \ \ \ \ \ \ \ \ \ \ \ \},\ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "alternateName":\ "nhpatt",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "birthDate":\ "1970-01-01T00:00:00Z",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "contactInformation":\ \{\},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "dateCreated":\ "2019-04-22T10:38:36Z",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "dateModified":\ "2019-04-22T10:38:37Z",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "emailAddress":\ "nhpatt@gmail.com",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "familyName":\ "Gamarra",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "givenName":\ "Javier",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 59347,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Javier\ Gamarra",\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "roleBriefs":\ {[}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20112,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "User"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ {]},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "siteBriefs":\ {[}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20128,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Global"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \},\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "id":\ 20124,\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "name":\ "Guest"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ {]}\ \ \ \ \ \ \ \ \ \ \ \ \ \}\ \ \ \ \ \ \ \ \ \ \ {]},\ \ \ \ \ \ \ \ \ \ \ "lastPage":\ 1,\ \ \ \ \ \ \ \ \ \ \ "page":\ 1,\ \ \ \ \ \ \ \ \ \ \ "pageSize":\ 20,\ \ \ \ \ \ \ \ \ \ \ "totalCount":\ 2\ \ \ \ \ \ \ \ \ \}}
\item
In the response, locate the ID of the element you want and look in the
OpenAPI profile for the appropriate GET item endpoint. For example,
the \texttt{user-accounts} GET item endpoint is
\texttt{/user-accounts/\{userAccountId\}}.
\item
Send a GET request to that endpoint. For example, this request gets
information for the user with the ID \texttt{59347} (Javier Gamarra):
\begin{verbatim}
curl "http://localhost:8080/o/headless-admin-user/v1.0/user-accounts/59347" -u 'test@example.com:test'
\end{verbatim}
\end{enumerate}
\section{Related Topics}\label{related-topics-129}
\href{/docs/7-2/frameworks/-/knowledge_base/f/getting-collections}{Getting
Collections}
\href{/docs/7-2/frameworks/-/knowledge_base/f/pagination}{Pagination}
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-authenticated-requests}{Making
Authenticated Requests}
\chapter{API Formats and Content
Negotiation}\label{api-formats-and-content-negotiation}
The responses in the preceding examples use a standard JSON format,
which is the default response format for Liferay DXP's headless REST
APIs. You can also use other formats like XML. Formats typically differ
in the resource metadata's structure or semantics. There's no best
format; use the one that best fits your use case.
You use \emph{content negotiation} to specify different formats for use.
Content negotiation is how the client and server establish the format
they use to exchange messages. The client tells the server its preferred
format via the HTTP headers \texttt{Accept} and \texttt{Content-Type}.
Each format has a string identifier (its MIME type) that you can use in
the HTTP headers to specify the format. The following table lists the
MIME type for each supported format.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.2812}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.7188}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
API Format
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
~MIME Type
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
application/json &
\href{https://www.iana.org/assignments/media-types/application/json}{application/json} \\
application/xml &
\href{https://www.iana.org/assignments/media-types/application/xml}{application/xml} \\
\end{longtable}
\noindent\hrulefill
When you send a request without specifying the API format, the server
responds with the default JSON. For example, here's such a request for a
list of folders from the Site with the ID \texttt{20124}:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20124/document-folders" -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"items": [
{
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T10:21:20Z",
"dateModified": "2019-04-22T10:21:20Z",
"id": 59319,
"name": "REST APIs Documentation",
"numberOfDocumentFolders": 0,
"numberOfDocuments": 0,
"siteId": 20124
}
],
"lastPage": 1,
"page": 1,
"pageSize": 20,
"totalCount": 1
}
\end{verbatim}
If you request the headers, the \texttt{Content-Type} response attribute
lists the content type's format (JSON, in this case):
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20124/document-folders" -u 'test@example.com:test' --head
\end{verbatim}
\begin{verbatim}
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1
Set-Cookie: JSESSIONID=9F61AEB8721DD9149BD577ECBC31AE3F; Path=/; HttpOnly
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: private, no-cache, no-store, must-revalidate
Pragma: no-cache
Set-Cookie: COOKIE_SUPPORT=true; Max-Age=31536000; Expires=Tue, 21-Apr-2020 10:23:57 GMT; Path=/; HttpOnly
Set-Cookie: GUEST_LANGUAGE_ID=en_US; Max-Age=31536000; Expires=Tue, 21-Apr-2020 10:23:57 GMT; Path=/; HttpOnly
Date: Mon, 22 Apr 2019 10:23:57 GMT
Content-Type: application/json
Transfer-Encoding: chunked
\end{verbatim}
To get the response in XML instead, specify \texttt{application/xml} in
the request's \texttt{Accept} header. Note that the XML response
includes the same information as JSON, but is structured differently:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/documents/59203" -H 'Accept: application/xml' -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
Test
Test
20130
Test Test
/web/test
2019-04-22T10:21:20Z
2019-04-22T10:21:20Z
59319
REST APIs Documentation
0
0
20124
1
1
20
1
\end{verbatim}
Requesting the headers, you can see that the response is in XML
(\texttt{application/xml}):
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/documents/59203" -H 'Accept: application/xml' -u 'test@example.com:test' --head
\end{verbatim}
\begin{verbatim}
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: private, no-cache, no-store, must-revalidate
Pragma: no-cache
Date: Mon, 22 Apr 2019 10:26:21 GMT
Content-Type: application/xml
Transfer-Encoding: chunked
\end{verbatim}
\section{Language Negotiation}\label{language-negotiation}
The same mechanism used for requesting another response format (content
negotiation) is used for requesting content in another language.
APIs that are available in different languages return the options in a
block called \texttt{availableLanguages}. For example, this block in the
following response lists U.S. English (\texttt{en-US}) and
Spain/Castilian Spanish (\texttt{es-ES}):
\begin{verbatim}
{
"availableLanguages": [
"en-US",
"es-ES"
],
"contentFields": [
{
"dataType": "html",
"name": "content",
"repeatable": false,
"value": {
"data": "The main reason is because Headless APIs have been designed with real use cases in mind...
"
}
}
],
"contentStructureId": 36801,
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T10:29:40Z",
"dateModified": "2019-04-22T10:30:31Z",
"datePublished": "2019-04-22T10:28:00Z",
"friendlyUrlPath": "why-headless-apis-are-better-than-json-ws-services-",
"id": 59325,
"key": "59323",
"numberOfComments": 0,
"renderedContents": [
{
"renderedContentURL": "http://localhost:8080/o/headless-delivery/v1.0/structured-contents/59325/rendered-content/36804",
"templateName": "Basic Web Content"
}
],
"siteId": 20124,
"title": "Why Headless APIs are better than JSON-WS services?",
"uuid": "e1c4c152-e47c-313f-2d16-2ee4eba5cd26"
}
\end{verbatim}
To request the content in another language, specify your desired locale
in the request's \texttt{Accept-Language} header:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/structured-contents/59325" -H 'Accept-Language: es-ES' -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"availableLanguages": [
"en-US",
"es-ES"
],
"contentFields": [
{
"dataType": "html",
"name": "content",
"repeatable": false,
"value": {
"data": "La principal razón es porque las APIs Headless se han diseñado pensando en casos de uso reales...
"
}
}
],
"contentStructureId": 36801,
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T10:29:40Z",
"dateModified": "2019-04-22T10:30:31Z",
"datePublished": "2019-04-22T10:28:00Z",
"friendlyUrlPath": "%C2%BFpor-qu%C3%A9-las-apis-headless-son-mejores-que-json-ws-",
"id": 59325,
"key": "59323",
"numberOfComments": 0,
"renderedContents": [
{
"renderedContentURL": "http://localhost:8080/o/headless-delivery/v1.0/structured-contents/59325/rendered-content/36804",
"templateName": "Contenido web básico"
}
],
"siteId": 20124,
"title": "¿Por qué las APIs Headless son mejores que JSON-WS?",
"uuid": "e1c4c152-e47c-313f-2d16-2ee4eba5cd26"
}
\end{verbatim}
\section{Creating Content with Different
Languages}\label{creating-content-with-different-languages}
By default, when sending a POST/PUT request, the
\texttt{Accept-Language} header is used as the content's language.
However, there is one exception. Some entities require the first POST to
be in the Site's default language. In such cases, a POST request for a
different language results in an error.
After creating a new resource, PUT requests in a different language adds
that translation. PATCH requests return an error (you are expected to
update, not create, in a PATCH request).
\section{Related Topics}\label{related-topics-130}
\href{/docs/7-2/frameworks/-/knowledge_base/f/get-started-discover-the-api}{Get
Started: Discover the API}
\href{/docs/7-2/frameworks/-/knowledge_base/f/get-started-invoke-a-service}{Get
Started: Invoke a Service}
\chapter{OpenAPI Profiles}\label{openapi-profiles}
All the APIs exposed by Liferay DXP are available under the
\href{https://app.swaggerhub.com/organizations/liferayinc}{liferayinc
SwaggerHub organization}.
Liferay DXP's headless APIs are categorized in two different use cases:
\begin{itemize}
\tightlist
\item
Delivering content (delivery APIs)
\item
Managing and administering content (admin APIs)
\end{itemize}
The available APIs demonstrate this categorization.
\section{Headless Delivery}\label{headless-delivery}
The following table lists the APIs that
\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0}{Headless
Delivery} contains. Note that the second column shows which internal
model in Liferay DXP that the API maps to.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.2812}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.7188}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
API
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
~Internal Model
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{BlogPosting} & \texttt{BlogsEntry} \\
\texttt{BlogPostingImage} & \texttt{DLFileEntry} (associated with a
\texttt{BlogsEntry}) \\
\texttt{Comment} & \texttt{DiscussionComment} \\
\texttt{ContentDocument} & \texttt{DLFileEntry} (associated with a
\texttt{JournalArticle}) \\
\texttt{ContentSet} & \texttt{AssetListEntry} \\
\texttt{ContentStructure} & \texttt{DDMStructure} \\
\texttt{Document} & \texttt{DLFileEntry} \\
\texttt{DocumentFolder} & \texttt{Folder} \\
\texttt{KnowledgeBaseArticle} & \texttt{KBArticle} \\
\texttt{KnowledgeBaseAttachment} & \texttt{FileEntry} (associated with a
\texttt{KBArticle}) \\
\texttt{KnowledgeBaseFolder} & \texttt{KBFolder} \\
\texttt{MessageBoardAttachment} & \texttt{FileEntry} (associated with a
\texttt{MBMessage}) \\
\texttt{MessageBoardMessage} & \texttt{MBMessage} \\
\texttt{MessageBoardSection} & \texttt{MBCategory} \\
\texttt{MessageBoardThread} & \texttt{MBThread} \\
\texttt{Rating} & \texttt{RatingsEntry} \\
\texttt{StructuredContent} & \texttt{JournalArticle} \\
\texttt{StructuredContentFolder} & \texttt{JournalFolder} \\
\end{longtable}
\noindent\hrulefill
\section{Headless Administration}\label{headless-administration}
There are several headless admin APIs, each containing its own set of
APIs. The following tables list these, as well as any internal models in
Liferay DXP that each API maps to.
\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-user/1.0}{Headless
Admin User} contains the following APIs for retrieving and managing
information about users and organizations.
\noindent\hrulefill
\begin{longtable}[]{@{}ll@{}}
\toprule\noalign{}
API & ~Internal Model \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{EmailAddress} & N/A \\
\texttt{Organization} & N/A \\
\texttt{Phone} & N/A \\
\texttt{PostalAddress} & \texttt{Address} \\
\texttt{Role} & N/A \\
\texttt{Segment} & \texttt{SegmentEntry} \\
\texttt{SegmentUser} & N/A \\
\texttt{SiteBrief} & N/A \\
\texttt{UserAccount} & \texttt{User} \\
\texttt{WebUrl} & \texttt{WebSite} \\
\end{longtable}
\noindent\hrulefill
\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/1.0}{Headless
Admin Taxonomy} contains the following APIs for managing asset
categories, asset vocabularies, and asset tags.
\noindent\hrulefill
\begin{longtable}[]{@{}ll@{}}
\toprule\noalign{}
API & ~Internal Model \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{Keyword} & \texttt{AssetTag} \\
\texttt{TaxonomyCategory} & \texttt{AssetCategory} \\
\texttt{TaxonomyVocabulary} & \texttt{AssetVocabulary} \\
\end{longtable}
\noindent\hrulefill
\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-workflow/1.0}{Headless
Admin Workflow} contains APIs for transitioning workflows.
\section{Related Topics}\label{related-topics-131}
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}
\chapter{Filter, Sort, and Search}\label{filter-sort-and-search}
You can use Liferay DXP's headless REST APIs to search for content
you're interested in. You can also sort and filter content. Here, you'll
learn how.
\section{Filter}\label{filter}
It's often useful to filter large collections for the exact data that
you need. Not all collections, however, allow filtering. The ones that
support it contain the optional parameter \texttt{filter} in their
OpenAPI profile. To filter a collection based on the value of one or
more fields, use the \texttt{filter} parameter following a subset of the
\href{https://docs.oasis-open.org/odata/odata/v4.01/csprd06/part1-protocol/odata-v4.01-csprd06-part1-protocol.html\#sec_BuiltinFilterOperations}{oData
standard}.
Filtering mainly applies to fields indexed as keywords in Liferay DXP's
search. To find content by terms contained in fields indexed as text,
you should instead use \hyperref[search]{search}.
\section{Comparison Operators}\label{comparison-operators}
\noindent\hrulefill
Operator \textbar{} Description \textbar{} Example \textbar{}
\texttt{eq} \textbar{} Equal \textbar{}
\texttt{addressLocality\ eq\ \textquotesingle{}Redmond\textquotesingle{}}
\textbar{} \textbar{} Equal null \textbar{}
\texttt{addressLocality\ eq\ null} \textbar{} \texttt{ne} \textbar{} Not
equal \textbar{}
\texttt{addressLocality\ ne\ \textquotesingle{}London\textquotesingle{}}
\textbar{} \textbar{} Not null \textbar{}
\texttt{addressLocality\ ne\ null} \textbar{} \texttt{gt} \textbar{}
Greater than \textbar{} \texttt{price\ gt\ 20} \textbar{} \texttt{ge}
\textbar{} Greater than or equal \textbar{} \texttt{price\ ge\ 10}
\textbar{} \texttt{lt} \textbar{} Less than \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} \texttt{le}
\textbar{} Less than or equal \textbar{}
\texttt{dateCreated\ le\ 2012-05-29T09:13:28Z} \textbar{}
\texttt{startswith} \textbar{} Starts with \textbar{}
\texttt{startswith(addressLocality,\ \textquotesingle{}Lond\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{Logical Operators}\label{logical-operators}
\noindent\hrulefill
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Operator & Description & Example \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{and} & Logical and &
\texttt{price\ le\ 200\ and\ price\ gt\ 3.5} \\
\texttt{or} & Logical or &
\texttt{price\ le\ 3.5\ or\ price\ gt\ 200} \\
\texttt{not} & Logical not & \texttt{not\ (price\ le\ 3.5)} \\
\end{longtable}
\noindent\hrulefill
Note that the \texttt{not} operator needs a space character after it.
\section{Grouping Operators}\label{grouping-operators}
\noindent\hrulefill
Operator \textbar{} Description \textbar{} Example \textbar{}
\texttt{(\ )} \textbar{} Precedence grouping \textbar{}
\texttt{(price\ eq\ 5)\ or\ (addressLocality\ eq\ \textquotesingle{}London\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{String Functions}\label{string-functions}
\noindent\hrulefill
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Function & Description & Example \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{contains} & Contains &
\texttt{contains(title,\textquotesingle{}edmon\textquotesingle{})} \\
\end{longtable}
\noindent\hrulefill
\section{Lambda Operators}\label{lambda-operators}
Lambda operators evaluate a boolean expression on a collection. They
must be prepended with a navigation path that identifies a collection.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2206}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.1618}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.6176}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Lambda Operator
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Example
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{any} & Any &
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))} \\
\end{longtable}
\noindent\hrulefill
The \texttt{any} operator applies a boolean expression to each
collection element and evaluates to \texttt{true} if the expression is
true for any element.
\section{Operator combinations and OData
syntax}\label{operator-combinations-and-odata-syntax}
Syntax examples and other operator combinations are covered in the
\href{https://docs.oasis-open.org/odata/odata/v4.01/csprd06/part1-protocol/odata-v4.01-csprd06-part1-protocol.html\#sec_BuiltinFilterOperations}{OData
standard reference}.
\section{Escaping in Queries}\label{escaping-in-queries}
You can escape a single quote in a value by adding another single quote.
For example, to filter for a blog posting whose headline is
\texttt{New\ Headless\ APIs}, append this filter string to the request
URL:
\begin{verbatim}
?filter=headline eq 'New Headless APIs'
\end{verbatim}
Here's an example of the full request:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20124/blog-postings/?filter=headline%20eq%20%27New%20Headless%20APIs%27" -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"items": [
{
"alternativeHeadline": "The power of OpenAPI & Liferay",
"articleBody": "We are happy to announce...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"dateModified": "2019-04-22T07:04:51Z",
"datePublished": "2019-04-22T07:02:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "new-headless-apis",
"headline": "New Headless APIs",
"id": 59301,
"numberOfComments": 0,
"siteId": 20124
}
],
"lastPage": 1,
"page": 1,
"pageSize": 20,
"totalCount": 1
}
\end{verbatim}
\section{Filtering in Structured Content Fields
(ContentField)}\label{filtering-in-structured-content-fields-contentfield}
To filter for a \texttt{ContentField} value (dynamic values created by
the end user), you must use the endpoints that are scoped to an
individual \texttt{ContentStructure}. To do so, find the ID of the
\texttt{ContentStructure} and use it in place of
\texttt{\{contentStructureId\}} in this URL:
\begin{verbatim}
"/content-structures/{contentStructureId}/structured-contents"
\end{verbatim}
\section{Search}\label{search-2}
It's often useful to search large collections with keywords. Use search
when you want results from any field, rather than specific ones. To
perform a search, use the optional parameter \texttt{search} followed by
the search terms. For example, this request searches for all the
\texttt{BlogEntry} fields containing OAuth:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20124/blog-postings/?search=OAuth" -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"items": [
{
"alternativeHeadline": "How to work with OAuth",
"articleBody": "To configure OAuth...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T09:35:09Z",
"dateModified": "2019-04-22T09:35:09Z",
"datePublished": "2019-04-22T09:34:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "authenticated-requests",
"headline": "Authenticated requests",
"id": 59309,
"numberOfComments": 0,
"siteId": 20124
}
],
"lastPage": 1,
"page": 1,
"pageSize": 20,
"totalCount": 1
}
\end{verbatim}
\section{Sorting}\label{sorting}
Sorting collection results is another common task. Note, however, that
not all collections allow sorting. The ones that support it contain the
optional parameter \texttt{\{lb\}?sort\{rb\}} in their OpenAPI profile.
To get sorted collection results, append
\texttt{?sort=\textless{}param-name\textgreater{}} to the request URL.
For example, appending \texttt{?sort=title} to the request URL sorts the
results by title.
The default sort order is ascending (0-1, A-Z). To perform a descending
sort, append \texttt{:desc} to the parameter name. For example, to
perform a descending sort by title, append \texttt{?sort=title:desc} to
the request URL.
To sort by more than one parameter, separate the parameter names by
commas and put them in order of priority. For example, to sort first by
title and then by creation date, append \texttt{?sort=title,dateCreated}
to the request URL.
To specify a descending sort for only one parameter, you must explicitly
specify ascending sort order (\texttt{:asc}) for the other parameters.
For example:
\begin{verbatim}
?sort=headline:desc,dateCreated:asc
\end{verbatim}
\section{Flatten}\label{flatten}
Some collections (as defined in their OpenAPI profile) allow the query
parameter \texttt{flatten}, which returns all resources and disregards
folders or other hierarchical classifications. This parameter's default
value is \texttt{false}, so a document query to the root folder returns
only the documents in that folder. With \texttt{flatten} set to
\texttt{true}, the same query also returns documents in any subfolders,
regardless of how deeply those folders are nested. In other words,
setting \texttt{flatten} set to \texttt{true} and querying for documents
in a Site's root folder returns all the documents in the Site.
\section{Related Topics}\label{related-topics-132}
\href{/docs/7-2/frameworks/-/knowledge_base/f/filter-sort-and-search}{Making
Authenticated Requests}
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}
\chapter{Restrict Properties}\label{restrict-properties}
Retrieving large entities or collections increases the response's size
and uses more bandwidth. You can alleviate this by telling the server
via the request which fields it should include in the response. This is
known as \emph{sparse fieldsets}. To make a request with sparse
fieldsets, include the \texttt{fields} parameter in the URL with the
name of each field's attribute.
For example, this request doesn't use sparse fieldsets and therefore
returns all the fields of a blog posting:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/59301" -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"alternativeHeadline": "The power of OpenAPI & Liferay",
"articleBody": "We are happy to announce...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"dateModified": "2019-04-22T07:04:51Z",
"datePublished": "2019-04-22T07:02:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "new-headless-apis",
"headline": "New Headless APIs",
"id": 59301,
"numberOfComments": 0,
"siteId": 20124
}
\end{verbatim}
To get only the headline, creation date, and creator, append the
\texttt{fields} parameter to the URL with the fields \texttt{headline},
\texttt{dateCreated}, and \texttt{creator}:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/59301?fields=headline,dateCreated,creator" -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"headline": "New Headless APIs"
}
\end{verbatim}
In the response, the \texttt{creator} attribute is a nested JSON object.
To return only the creator's name, specify that nested field via dot
notation (\texttt{creator.name}):
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/59301?fields=headline,dateCreated,creator.name" -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"creator": {
"name": "Test Test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"headline": "New Headless APIs"
}
\end{verbatim}
The \texttt{fields} parameter also works with collection resources to
return the specified attributes for every collection item. For example,
this request gets the headlines for all the blog postings in the Site
with the ID \texttt{20124}:
\begin{verbatim}
curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20124/blog-postings/?fields=headline" -u 'test@example.com:test'
\end{verbatim}
\begin{verbatim}
{
"items": [
{
"headline": "New Headless APIs"
},
{
"headline": "Authenticated requests"
}
],
"lastPage": 1,
"page": 1,
"pageSize": 20,
"totalCount": 2
}
\end{verbatim}
\section{Related Topics}\label{related-topics-133}
\href{/docs/7-2/frameworks/-/knowledge_base/f/filter-sort-and-search}{Making
Authenticated Requests}
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}
\chapter{Multipart Requests}\label{multipart-requests}
Several operations accept a binary file via a multipart request. For
example, the definition for posting a file to a \texttt{DocumentFolder}
specifies a multipart request:
\begin{verbatim}
post:
operationId: postDocumentFolderDocument
parameters:
- in: path
name: documentFolderId
required: true
schema:
format: int64
type: integer
requestBody:
content:
multipart/form-data:
schema:
properties:
document:
$ref: "#/components/schemas/Document"
file:
format: binary
type: string
type: object
responses:
200:
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
application/xml:
schema:
$ref: "#/components/schemas/Document"
description: ""
tags: ["Document"]
\end{verbatim}
This operation returns a \texttt{Document} (in JSON or XML). To create
this \texttt{Document}, you must supply the operation's multipart
request with 2 components:
\begin{itemize}
\tightlist
\item
A binary file (bytes) via the \texttt{file} property
\item
A JSON string with the binary file's metadata, via the
\texttt{document} property
\end{itemize}
To send this request, the \texttt{Content-Type} must be
\texttt{multipart/form-data}, and you must also specify a boundary name
(the boundary name can be arbitrary).
Here's an example request (without the file's bytes) that creates a
document in the folder with the ID \texttt{38549}:
\begin{verbatim}
curl -X "POST" "http://localhost:8080/o/headless-delivery/v1.0/document-folders/38549/documents" \
-H 'Accept: application/json' \
-H 'Content-Type: multipart/form-data; boundary=PART' \
-u 'test@example.com:test' \
-F "file=" \
-F "document={\"title\": \"podcast\"}"
\end{verbatim}
And here's the response:
\begin{verbatim}
{
"contentUrl": "/documents/20123/38549/podcast.mp3/e978e316-620c-df9f-e0bd-7cc0447cca49?version=1.0&t=1556100111417",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20129,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-24T10:01:51Z",
"dateModified": "2019-04-24T10:01:51Z",
"documentFolderId": 38549,
"encodingFormat": "audio/mpeg",
"fileExtension": "mp3",
"id": 38553,
"numberOfComments": 0,
"sizeInBytes": 28482097,
"title": "podcast"
}
\end{verbatim}
\section{Related Topics}\label{related-topics-134}
\href{/docs/7-2/frameworks/-/knowledge_base/f/filter-sort-and-search}{Making
Authenticated Requests}
\href{/docs/7-2/frameworks/-/knowledge_base/f/api-formats-and-content-negotiation}{API
Formats and Content Negotiation}
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}
\chapter{How to get siteId}\label{how-to-get-siteid}
Several APIs (generally all collection APIs) need the \texttt{siteId}
parameter to to execute requests. The \texttt{siteId} is the internal
identifier of the Site where that content was created.
\section{Using siteId or siteKey}\label{using-siteid-or-sitekey}
In all the APIs available from 7.2 GA2+, the \texttt{siteKey} is also
accepted as a valid parameter. The \texttt{siteKey} is the external name
of the Site (for example \texttt{Guest}).
In the REST APIs, you can use \texttt{siteKey} in all the places that
expect a \texttt{siteId}; in GraphQL APIs, there are two different
parameters: \texttt{siteId} and \texttt{siteKey}.
Using the \texttt{siteKey} is recommended over \texttt{siteId} in all
situations because it's more recognizable, doesn't expose an internal
parameter, and doesn't change in import/export processes.
\section{Obtain siteId}\label{obtain-siteid}
There are several ways to retrieve the \texttt{siteId}:
\begin{itemize}
\item
Use the Site API to query it by name, friendly URL or in the list of a
User's Sites.
\item
From Liferay DXP's UI in the Site Administration menu (not
recommended).
\item
From the \texttt{Group} table in the database (not recommended).
\item
From the \texttt{ThemeDisplay} object in JavaScript or Java:
\begin{verbatim}
themeDisplay.getSiteGroupId()
\end{verbatim}
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/rest-site-id.png}
\caption{GraphQL BlogPostings definition}
\end{figure}
\chapter{Filterable properties}\label{filterable-properties}
Some APIs return data that can be filtered and sorted by several
properties. This is a non-comprehensive list of the properties that can
be used to filter or sort.
\section{Headless Delivery API}\label{headless-delivery-api}
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/BlogPosting}{BlogPosting}}{BlogPosting}}\label{blogposting}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} taxonomyCategoryIds
\textbar{} list \textbar{} \texttt{taxonomyCategoryIds/any(t:t\ eq\ 1)}
\textbar{} keywords \textbar{} list \textbar{}
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))}\textbar{}
customFields \textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} creatorId
\textbar{} integer \textbar{} \texttt{creatorId\ eq\ 1} \textbar{}
headline \textbar{} string \textbar{}
\texttt{contains(headline,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/BlogPostingImage}{BlogPostingImage}}{BlogPostingImage}}\label{blogpostingimage}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} encodingFormat
\textbar{} id \textbar{} \texttt{encodingFormat\ eq\ 1} \textbar{}
sizeInBytes \textbar{} integer \textbar{} \texttt{sizeInBytes\ eq\ 1}
\textbar{} fileExtension \textbar{} string \textbar{}
\texttt{contains(fileExtension,\textquotesingle{}substring1\textquotesingle{})}
\textbar{} title \textbar{} string \textbar{}
\texttt{contains(title,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/Comment}{Comment}}{Comment}}\label{comment}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} dateCreated \textbar{}
date \textbar{} \texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z}
\textbar{} dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z}\textbar{} creatorId
\textbar{} integer \textbar{} \texttt{creatorId\ eq\ 1} \textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/ContentStructure}{ContentStructure}}{ContentStructure}}\label{contentstructure}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} dateCreated \textbar{}
date \textbar{} \texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z}
\textbar{} dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} name
\textbar{} string \textbar{}
\texttt{contains(name,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/Document}{Document}}{Document}}\label{document}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} taxonomyCategoryIds
\textbar{} list \textbar{} \texttt{taxonomyCategoryIds/any(t:t\ eq\ 1)}
\textbar{} keywords \textbar{} list \textbar{}
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))}\textbar{}
customFields \textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{}
encodingFormat \textbar{} id \textbar{} \texttt{encodingFormat\ eq\ 1}
\textbar{} creatorId \textbar{} integer \textbar{}
\texttt{creatorId\ eq\ 1} \textbar{} sizeInBytes \textbar{} integer
\textbar{} \texttt{sizeInBytes\ eq\ 1} \textbar{} fileExtension
\textbar{} string \textbar{}
\texttt{contains(fileExtension,\textquotesingle{}substring1\textquotesingle{})}
\textbar{} title \textbar{} string \textbar{}
\texttt{contains(title,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/DocumentFolder}{DocumentFolder}}{DocumentFolder}}\label{documentfolder}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} customFields
\textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} creatorId
\textbar{} integer \textbar{} \texttt{creatorId\ eq\ 1} \textbar{} name
\textbar{} string \textbar{}
\texttt{contains(name,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/KnowledgeBaseArticle}{KnowledgeBaseArticle}}{KnowledgeBaseArticle}}\label{knowledgebasearticle}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} taxonomyCategoryIds
\textbar{} list \textbar{} \texttt{taxonomyCategoryIds/any(t:t\ eq\ 1)}
\textbar{} keywords \textbar{} list \textbar{}
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))}\textbar{}
customFields \textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} title
\textbar{} string \textbar{}
\texttt{contains(title,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/MessageBoardMessage}{MessageBoardMessage}}{MessageBoardMessage}}\label{messageboardmessage}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} showAsAnswer
\textbar{} boolean \textbar{} \texttt{showAsAnswer\ eq\ true} \textbar{}
showAsQuestion \textbar{} boolean \textbar{}
\texttt{showAsQuestion\ eq\ true} \textbar{} taxonomyCategoryIds
\textbar{} list \textbar{} \texttt{taxonomyCategoryIds/any(t:t\ eq\ 1)}
\textbar{} keywords \textbar{} list \textbar{}
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))}\textbar{}
customFields \textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} creatorId
\textbar{} integer \textbar{} \texttt{creatorId\ eq\ 1} \textbar{}
messageBoardSectionId \textbar{} integer \textbar{}
\texttt{messageBoardSectionId\ eq\ 1} \textbar{} headline \textbar{}
string \textbar{}
\texttt{contains(headline,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/MessageBoardSection}{MessageBoardSection}}{MessageBoardSection}}\label{messageboardsection}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} customFields
\textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} creatorId
\textbar{} integer \textbar{} \texttt{creatorId\ eq\ 1} \textbar{} title
\textbar{} string \textbar{}
\texttt{contains(title,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/StructuredContent}{StructuredContent}}{StructuredContent}}\label{structuredcontent}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} taxonomyCategoryIds
\textbar{} list \textbar{} \texttt{taxonomyCategoryIds/any(t:t\ eq\ 1)}
\textbar{} keywords \textbar{} list \textbar{}
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))}\textbar{}
contentFields \textbar{} complex \textbar{}
\texttt{contentFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} customFields \textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} datePublished
\textbar{} date \textbar{}
\texttt{datePublished\ lt\ 2018-02-13T12:33:12Z} \textbar{}
contentStructureId \textbar{} integer \textbar{}
\texttt{contentStructureId\ eq\ 1} \textbar{} title \textbar{} string
\textbar{}
\texttt{contains(title,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/StructuredContentFolder}{StructuredContentFolder}}{StructuredContentFolder}}\label{structuredcontentfolder}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} customFields
\textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} creatorId
\textbar{} integer \textbar{} \texttt{creatorId\ eq\ 1} \textbar{} name
\textbar{} string \textbar{}
\texttt{contains(name,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/WikiNode}{WikiNode}}{WikiNode}}\label{wikinode}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} dateCreated \textbar{}
date \textbar{} \texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z}
\textbar{} dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} creatorId
\textbar{} integer \textbar{} \texttt{creatorId\ eq\ 1} \textbar{} name
\textbar{} string \textbar{}
\texttt{contains(name,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0\#/WikiPage}{WikiPage}}{WikiPage}}\label{wikipage}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} customFields
\textbar{} complex \textbar{}
\texttt{customFields/Name\ eq\ \textquotesingle{}Article1\textquotesingle{}}
\textbar{} dateCreated \textbar{} date \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} dateModified
\textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} headline
\textbar{} string \textbar{}
\texttt{contains(headline,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{Headless Admin User API}\label{headless-admin-user-api}
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-user/v1.0\#/Organization}{Organization}}{Organization}}\label{organization}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} keywords \textbar{}
list \textbar{}
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))}\textbar{}
dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{}
parentOrganizationId \textbar{} id \textbar{}
\texttt{parentOrganizationId\ eq\ 1} \textbar{} name \textbar{} string
\textbar{}
\texttt{contains(name,\textquotesingle{}category\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-user/v1.0\#/User}{User}}{User}}\label{user}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} keywords \textbar{}
list \textbar{}
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))}\textbar{}
dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z} \textbar{} id \textbar{}
id \textbar{} \texttt{id\ eq\ 1} \textbar{} organizationIds \textbar{}
id \textbar{} \texttt{organizationIds\ eq\ 1} \textbar{} roleIds
\textbar{} id \textbar{} \texttt{roleIds\ eq\ 1} \textbar{} userGroupIds
\textbar{} id \textbar{} \texttt{userGroupIds\ eq\ 1} \textbar{}
alternateName \textbar{} string \textbar{}
\texttt{contains(alternateName,\textquotesingle{}substring1\textquotesingle{})}
\textbar{} emailAddress \textbar{} string \textbar{}
\texttt{contains(emailAddress,\textquotesingle{}substring1\textquotesingle{})}
\textbar{} familyName \textbar{} string \textbar{}
\texttt{contains(familyName,\textquotesingle{}substring1\textquotesingle{})}
\textbar{} givenName \textbar{} string \textbar{}
\texttt{contains(givenName,\textquotesingle{}substring1\textquotesingle{})}
\textbar{} jobTitle \textbar{} string \textbar{}
\texttt{contains(jobTitle,\textquotesingle{}substring1\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{Headless Admin Taxonomy API}\label{headless-admin-taxonomy-api}
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Category}{Category}}{Category}}\label{category}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} dateCreated \textbar{}
date \textbar{} \texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z}
\textbar{} dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z}\textbar{} name
\textbar{} string \textbar{}
\texttt{contains(name,\textquotesingle{}category\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Keyword}{Keyword}}{Keyword}}\label{keyword}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} dateCreated \textbar{}
date \textbar{} \texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z}
\textbar{} dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z}\textbar{} name
\textbar{} string \textbar{}
\texttt{contains(name,\textquotesingle{}category\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{\texorpdfstring{\href{https://app.swaggerhub.com/apis/liferayinc/headless-admin-taxonomy/v1.0\#/Vocabulary}{Vocabulary}}{Vocabulary}}\label{vocabulary}
\noindent\hrulefill
Key \textbar{} Type \textbar{} Example \textbar{} dateCreated \textbar{}
date \textbar{} \texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z}
\textbar{} dateModified \textbar{} date \textbar{}
\texttt{dateModified\ lt\ 2018-02-13T12:33:12Z}\textbar{} name
\textbar{} string \textbar{}
\texttt{contains(name,\textquotesingle{}category\textquotesingle{})}
\textbar{}
\chapter{Using REST APIs}\label{using-rest-apis}
Liferay DXP's headless REST APIs can be used with any REST client you
prefer. The only usual requirements are setting up the
\texttt{Authentication} header (either OAuth, Cookie, Basic\ldots) and
the \texttt{Content-Type} header if you are creating content.
Our recommendation for JavaScript applications is to use \texttt{fetch}
directly, like this:
\begin{verbatim}
fetch(`http://localhost:8080/o/headless-delivery/v1.0/sites/${SITE_ID}/structured-contents/'`,
{
method: 'GET',
headers: {
'Authorization': `Basic ${BASIC_AUTH}`
}
}
);
\end{verbatim}
Or for a \texttt{POST} request:
\begin{verbatim}
fetch(`http://localhost:8080/o/headless-delivery/v1.0/sites/${SITE_ID}/structured-contents/`,
{
method: 'POST',
headers: {
'Authorization': `Basic ${BASIC_AUTH}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(
{
"title": "New appointment",
"contentStructureId": STRUCTURE_ID,
"contentFields": [
{
"name": "User",
"value": {
"data": USER,
}
},
]
}
)
}
)
\end{verbatim}
Here are two examples of JavaScript applications using the Headless REST
APIs:
\begin{itemize}
\tightlist
\item
\href{https://github.com/dgomezg/liferay-frontend-samples/tree/master/riuvo-alexa-skill}{Alexa
skill using Headless REST APIs with node-fetch}.
\item
\href{https://liferay.dev/blogs/-/blogs/creating-headless-apis-part-1}{Example
API from scratch using REST Builder}.
\end{itemize}
\chapter{JAX-RS}\label{jax-rs}
JAX-RS web services work in Liferay modules the same way they work
outside of Liferay. The only difference is that you must register the
class in the OSGi framework. Liferay makes this easy by providing a
template.
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a project} and choose the \emph{rest} template.
The class that's generated contains a working JAX-RS web service. You
can deploy it and use it immediately.
While it's beyond the scope of this article to cover
\href{https://blog.osgi.org/2018/03/osgi-r7-highlights-jax-rs-whiteboard.html}{JAX-RS
Whiteboard} in its entirety, essentially it's JAX-RS unchanged except
for configuration properties in the \texttt{@Component} annotation.
These properties declare three things:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
The endpoint for the service
\item
The service name as it appears in the OAuth 2.0 configuration
\item
(Optional) Properties you may want to set for further configuration.
\end{enumerate}
The generated class contains this configuration:
\begin{verbatim}
@Component(
property = {
JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE + "=/greetings",
JaxrsWhiteboardConstants.JAX_RS_NAME + "=Greetings.Rest"
},
service = Application.class)
\end{verbatim}
This configuration registers the service at this endpoint:
\begin{verbatim}
https://[server-name]:[port]/o/greetings
\end{verbatim}
If you're testing this locally on Tomcat, the URL is
\begin{verbatim}
https://localhost:8080/o/greetings
\end{verbatim}
As you might guess, you don't have access to the service by just calling
the URL above. You must authenticate first, which you'll learn how to do
next.
\section{Authenticating to JAX-RS Web
Services}\label{authenticating-to-jax-rs-web-services}
Authentication during development can be done through Basic
Authentication or portal sessions, but you don't want to leave that
enabled for production. For production, you want OAuth 2.0. Here's how
to configure JAX-RS authentication.
\section{During Development: Basic
Auth}\label{during-development-basic-auth}
When you deploy a JAX-RS application, an
\href{/docs/7-2/deploy/-/knowledge_base/d/authentication-verifiers}{Auth
Verifier} filter is registered for it. You can set its properties in
your \texttt{@Component} annotation by prefixing the properties with
\texttt{auth.verifier}. For example, to disable guest access to the
service, configure it like this:
\begin{verbatim}
@Component(
property = {
JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE + "=/greetings",
JaxrsWhiteboardConstants.JAX_RS_NAME + "=Greetings.Rest",
"auth.verifier.guest.allowed=false"
},
service = Application.class)
\end{verbatim}
Basic Auth is great during development, but credentials passed on the
URL appear in server logs, so when you're done developing, you should
disable Basic Auth and use OAuth2 instead. To disable Basic Auth, create
and deploy a configuration file called
\texttt{com.liferay.portal.security.auth.verifier.internal.tracker.AuthVerifierFilterTracker.config}
that contains this property:
\begin{verbatim}
default.registration.property=["filter.init.auth.verifier.OAuth2RESTAuthVerifier.urls.includes=*","filter.init.auth.verifier.PortalSessionAuthVerifier.urls.includes=*"]
\end{verbatim}
This disables Basic Auth for all JAX-RS applications, but keeps Portal
Session and OAuth2 enabled.
\section{Using OAuth 2.0 to Invoke a JAX-RS Web
Service}\label{using-oauth-2.0-to-invoke-a-jax-rs-web-service}
Your JAX-RS web service requires authorization by default. To enable
this, you must create an
\href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0\#creating-an-application}{OAuth
2.0 application} to provide a way to grant access to your service:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to the \emph{Control Panel} → \emph{Configuration} → \emph{OAuth2
Administration} and click the \includegraphics{./images/icon-add.png}
button to add an application.
\item
Give your application a descriptive name.
\item
Choose the Client Profile appropriate for this service. These are
templates that auto-select the appropriate authorization types or
``flows'' from the OAuth 2 standard. For this example choose the
\emph{Headless Server} profile, which auto-selects the \emph{Client
Credentials} authorization type.
\item
Click \emph{Save}.
\end{enumerate}
The form now reappears with two additional generated fields: Client ID
and Client Secret. You'll use these to authenticate to your web service.
To make your service accessible,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Click the \emph{Scopes} tab.
\item
You'll see an entry for your deployed \texttt{Greetings.Rest} service.
Expand it by clicking the arrow.
\item
Check the box labeled \emph{read data on your behalf}.
\item
Click \emph{Save}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/jax-rs-oauth2-scope.png}
\caption{Enable the scope to grant access to the service.}
\end{figure}
For simplicity, the examples below use \href{https://curl.haxx.se}{Curl}
to authenticate. You need the two pieces of information generated for
your application: the Client ID and the Client Secret. For example, say
those fields contain these values:
\textbf{Client ID:} \texttt{id-12e14a84-e558-35a7-cf9a-c64aafc7f}
\textbf{Client Secret:}
\texttt{secret-93f14320-dc39-d67f-9dec-97717b814f}
First, you must request an OAuth token. If you're testing locally, you'd
make a request like this:
\begin{verbatim}
curl http://localhost:8080/o/oauth2/token -d 'grant_type=client_credentials&client_id=id-12e14a84-e558-35a7-cf9a-c64aafc7f&client_secret=secret-93f14320-dc39-d67f-9dec-97717b814f'
\end{verbatim}
The response is JSON:
\begin{verbatim}
{"access_token":"a7f12bef7f2e578cf64bce4085db8f17b6a3c2963f865a65b374e89784bbca5","token_type":"Bearer","expires_in":600,"scope":"GET POST PUT"}
\end{verbatim}
It contains a token, generated for this client. It expires in 600
seconds, and it grants GET, POST, and PUT for this web service.
When you want to call the service, you must supply the token in the HTTP
header, like this:
\begin{verbatim}
curl --header "Authorization: Bearer a7f12bef7f2e578cf64bce4085db8f17b6a3c2963f865a65b374e89784bbca5" http://localhost:8080/o/greetings/morning
\end{verbatim}
With authorization, your web service can be called and responds to the
request:
\begin{verbatim}
Good morning!
\end{verbatim}
Of course, this is only one of the authorization flows for OAuth 2.0. If
you're creating a web-based client whose back-end is a JAX-RS web
service hosted on Liferay DXP, you'd want one of the other flows. See
the \href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0}{OAuth 2.0
documentation} for further information. Additionally, OAuth 2.0 assumes
the use of HTTPS for its security: the above URLs are only for local
testing purposes. You certainly would not want to pass OAuth tokens
between clients and servers in the clear. Make sure that in production
your server uses HTTPS.
\subsection{OAuth2 Scopes}\label{oauth2-scopes}
Without any special Liferay OAuth2 annotations or properties, a standard
OSGi JAX-RS application is inspected by the Liferay OAuth2 runtime, and
scopes are derived by default based on the HTTP verbs supported by the
application.
When developers want more control, they can use the property
\texttt{oauth2.scopechecker.type=annotations} and the annotation
\texttt{com.liferay.oauth2.provider.scope.RequiresScope} exported from
the \texttt{Liferay\ OAuth2\ Provider\ Scope\ API} bundle to annotate
endpoint resource methods or whole classes like this:
\begin{verbatim}
@RequiresScope("scopeName")
\end{verbatim}
Once deployed, this becomes a scope in the
\href{/docs/7-2/deploy/-/knowledge_base/d/oauth2-scopes}{OAuth 2.0
configuration}. You can disable scope checking (not recommended) by
setting the scope checker to a non-existent type:
\begin{verbatim}
oauth2.scope.checker.type=none
\end{verbatim}
\subsection{Requiring OAuth2}\label{requiring-oauth2}
You can specify OAuth2 authorization as required for your JAX-RS
application by using this property:
\begin{verbatim}
osgi.jaxrs.extension.select=(osgi.jaxrs.name=Liferay.OAuth2)
\end{verbatim}
\section{JAX-RS and Service Access
Policies}\label{jax-rs-and-service-access-policies}
When authenticating via Basic Auth, the
\href{/docs/7-2/deploy/-/knowledge_base/d/service-access-policies}{Service
Access Policy} \texttt{SYSTEM\_USER\_PASSWORD} is enforced. When
authenticating via OAuth 2.0, the \texttt{AUTHORIZED\_OAUTH2\_SAP}
policy is enforced. Configure them appropriately for your environment,
as by default, they allow invoking all remote services. To disable
Service Access Policy enforcement for JAX-RS endpoints (not
recommended), set this property:
\begin{verbatim}
liferay.access.control.disable=true
\end{verbatim}
With this configured, guests can call these endpoints without
administrators having to define a default Service Access Policy.
\section{Public JAX-RS Services}\label{public-jax-rs-services}
To create a public endpoint for development purposes, all you must do is
set two properties:
\begin{verbatim}
@Component(
property={
"auth.verifier.guest.allowed=true",
"liferay.access.control.disable=true"
},
service = Application.class
)
\end{verbatim}
Don't keep this configuration for production. For public services, it's
best to leave the security in place and whitelist the particular
endpoints you're making public. See
\href{/docs/7-2/deploy/-/knowledge_base/d/service-access-policies}{Service
Access Policies} for further information.
\section{Using JAX-RS with CORS}\label{using-jax-rs-with-cors}
If you foresee that JavaScript in a browser might access your JAX-RS web
service from a different domain, you might want to use the CORS
annotation. You can use the \texttt{@CORS} annotation to define
\href{/docs/7-2/deploy/-/knowledge_base/d/configuring-cors}{CORS
policies} on your deployed JAX-RS applications. Note that the
annotations
\href{/docs/7-2/deploy/-/knowledge_base/d/configuring-cors}{can be
overridden by an administrator}. It only takes three steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Add the Portal Remote CORS API dependency to your module:
\end{enumerate}
\begin{verbatim}
compileOnly project(":apps:portal-remote:portal-remote-cors-api")
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
Activate the CORS annotation feature in your application properties:
\end{enumerate}
\begin{verbatim}
@Component(
property = {
"osgi.jaxrs.application.base=/my-application",
"osgi.jaxrs.name=My.Application.Name",
"liferay.cors.annotation=true"
},
service = Application.class
)
public class MyApplication extends Application {
...
}
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
Use the \texttt{@CORS} annotation throughout your application globally
or by method.
\end{enumerate}
Globally:
\begin{verbatim}
@Component(
property = {
"osgi.jaxrs.application.base=/my-application",
"osgi.jaxrs.name=My.Application.Name",
"liferay.cors.annotation=true"
},
service = Application.class
)
@CORS(allowMethods="GET")
public class MyApplication extends Application {
...
}
\end{verbatim}
By method:
\begin{verbatim}
@CORS
@GET
@Path("/users")
public List getUserList() throws Exception {
return _users;
}
\end{verbatim}
You can use the annotation to provide a configuration for any of the
CORS headers. Here are some examples:
\noindent\hrulefill
\begin{verbatim}
Header | Annotation Example |
\end{verbatim}
Access-Control-Allow-Credentials\textbar{}\texttt{@CORS(allowCredentials\ =\ false)}\textbar{}
Access-Control-Allow-Headers\textbar{}\texttt{@CORS(allowHeaders\ =\ "X-PINGOTHER")}\textbar{}
Access-Control-Allow-Methods\textbar{}\texttt{@CORS(allowMethods\ =\ "OPTIONS,POST")}\textbar{}
Access-Control-Allow-Origin\textbar{}\texttt{@CORS(allowOrigin\ =\ "http://www.liferay.com")}\textbar{}
\noindent\hrulefill
If for some reason you want to disable the \texttt{@CORS} annotations in
your application, you can do it globally by disabling it in your
\texttt{@Component} annotation:
\begin{verbatim}
@Component(
property = {
"osgi.jaxrs.application.base=/no-cors-application",
"osgi.jaxrs.name=NoCors.Application.Name",
"liferay.cors.annotation=false"
},
service = Application.class
)
\end{verbatim}
Great! Now you know how to create, deploy, and invoke JAX-RS web
services on Liferay DXP's platform!
\section{Related Topics}\label{related-topics-135}
\href{/docs/7-2/appdev/-/knowledge_base/a/rest-builder}{REST Builder}
\chapter{JAX-WS}\label{jax-ws}
Liferay supports
\href{https://en.wikipedia.org/wiki/Java_API_for_XML_Web_Services}{JAX-WS}
via the \href{http://cxf.apache.org/}{Apache CXF} implementation. Apps
can publish JAX-WS web services to the CXF endpoints defined in your
Liferay instance. CXF endpoints are effectively context paths the JAX-WS
web services are deployed to and accessible from. To publish any kind of
JAX-WS web service, one or more CXF endpoints must be defined. To access
JAX-WS web services, an \emph{extender} must also be configured in your
Liferay instance. Extenders specify where the services are deployed and
whether they are augmented with handlers, providers, and so on.
\textbf{SOAP Extenders:} Required to publish JAX-WS web services. Each
SOAP extender can deploy the services to one or more CXF endpoints and
can use a set of
\href{https://jax-ws.java.net/articles/handlers_introduction.html}{JAX-WS
handlers} to augment the services.
SOAP extenders are subsystems that track the services the app developer
registers in OSGi (those matching the provided
\href{https://osgi.org/javadoc/r6/core/org/osgi/framework/Filter.html}{OSGi
filters}), and deploy them under the specified CXF endpoints. For
example, if you create the CXF endpoint \texttt{/soap}, you could later
create a SOAP extender for \texttt{/soap} that publishes SOAP services.
Of course, this is only a rough example: you can fine tune things to
your liking.
CXF endpoints and extenders can be created programmatically or with
Liferay's Control Panel. This tutorial shows you how to do both, and
then shows you how to publish JAX-WS web services. The following topics
are covered:
\begin{itemize}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/jax-ws\#configuring-endpoints-and-extenders-with-the-control-panel}{Configuring
Endpoints and Extenders with the Control Panel}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/jax-ws\#configuring-endpoints-and-extenders-programmatically}{Configuring
Endpoints and Extenders Programmatically}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/jax-ws\#publishing-jax-ws-web-services}{Publishing
JAX-WS Web Services}
\end{itemize}
\section{Configuring Endpoints and Extenders with the Control
Panel}\label{configuring-endpoints-and-extenders-with-the-control-panel}
Liferay's Control Panel lets administrators configure endpoints and
extenders for JAX-WS web services. Note that you must be an
administrator in your Liferay instance to access the settings here.
First, you'll learn how to create CXF endpoints.
To configure a CXF endpoint with the Control Panel, first go to
\emph{Control Panel} → \emph{Configuration} → \emph{System Settings} →
\emph{Web API}. Then select \emph{CXF Endpoints} from the list. If there
are any existing CXF endpoints, they're shown here. To add a new one,
click the \emph{Add} button. The form that appears lets you configure a
new CXF endpoint by filling out these fields:
\textbf{Context Path:} The path the JAX-WS web services are deployed to
on the Liferay server. For example, if you define the context path
\texttt{/web-services}, any services deployed there are available at
\texttt{http://your-server:your-port/o/web-services}.
\textbf{\texttt{AuthVerifier} properties:} Any properties defined here
are passed as-is to the \texttt{AuthVerifier} filter. See the
\href{/docs/7-2/deploy/-/knowledge_base/d/authentication-verifiers}{\texttt{AuthVerifier}
documentation} for more details.
\textbf{Required Extensions:} CXF normally loads its default extension
classes, but in some cases you can override them to replace the default
behavior. In most cases, you can leave this field blank: overriding
extensions isn't common. By specifying custom extensions here via
\href{https://osgi.org/javadoc/r6/core/org/osgi/framework/Filter.html}{OSGi
filters}, Liferay waits until those extensions are registered in the
OSGi framework before creating the CXF servlet and passing the
extensions to the servlet.
\begin{figure}
\centering
\includegraphics{./images/cxf-endpoint-form.png}
\caption{Fill out this form to create a CXF endpoint.}
\end{figure}
For an app to deploy JAX-WS web services, you must configure a SOAP
extender. To configure a SOAP extender with the Control Panel, first go
to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}
→ \emph{Web API}. Then select \emph{SOAP Extenders} from the list. If
there are any existing SOAP extenders, they're shown here. To add a new
one, click on the \emph{Add} button. The form that appears lets you
configure a new SOAP extender by filling out these fields:
\textbf{Context paths:} Specify at least one CXF endpoint here. This is
where the services affected by this extender are deployed. In the
preceding CXF endpoint example, this would be \texttt{/web-services}.
Note that you can specify more than one CXF endpoint here.
\textbf{jax.ws.handler.filters:} Here you can specify a set of
\href{https://osgi.org/javadoc/r6/core/org/osgi/framework/Filter.html}{OSGi
filters} that select certain services registered in the OSGi framework.
The selected services should implement JAX-WS handlers and augment the
JAX-WS services specified in the \emph{jax.ws.service.filters} property.
These JAX-WS handlers apply to each service selected in this extender.
\textbf{jax.ws.service.filters:} Here you can specify a set of OSGi
filters that select the services registered in the OSGi framework that
are deployed to the CXF endpoints. These OSGi services must be
\href{https://docs.oracle.com/javaee/7/tutorial/jaxws001.htm}{proper
JAX-WS services}.
\textbf{soap.descriptor.builder:} Leave this option empty to use JAX-WS
annotations to describe the SOAP service. To use a different way to
describe the SOAP service, you can provide an OSGi filter here that
selects an implementation of
\texttt{com.liferay.portal.remote.soap.extender.SoapDescriptorBuilder}.
\begin{figure}
\centering
\includegraphics{./images/soap-extenders-form.png}
\caption{Fill out this form to create a SOAP extender.}
\end{figure}
Next, you'll learn how to create endpoints and extenders
programmatically.
\section{Configuring Endpoints and Extenders
Programmatically}\label{configuring-endpoints-and-extenders-programmatically}
To configure endpoints or extenders programmatically, you must use
Liferay's configurator extender. The configurator extender provides a
way for OSGi modules to deploy default configuration values. Modules
that use the configurator extender must provide a
\texttt{ConfigurationPath} header that points to the configuration
files' location inside the module. For example, the following
configuration sets the \texttt{ConfigurationPath} to
\texttt{src/main/resources/configuration}:
\begin{verbatim}
Bundle-Name: Liferay Export Import Service JAX-WS
Bundle-SymbolicName: com.liferay.exportimport.service.jaxws
Bundle-Version: 1.0.0
Liferay-Configuration-Path: /configuration
Include-Resource: configuration=src/main/resources/configuration
Liferay-Releng-Module-Group-Description:
Liferay-Releng-Module-Group-Title: Data Management
\end{verbatim}
Note that Liferay-specific Bnd instructions are prefixed with
\texttt{Liferay} to avoid conflicts.
There are two different configuration types in
\href{https://osgi.org/javadoc/r4v42/org/osgi/service/cm/ConfigurationAdmin.html}{OSGi's
\texttt{ConfigurationAdmin}}: single, and factory. Factory
configurations can have several configuration instances per factory
name. Liferay DXP uses factory configurations. You must provide a
factory configuration's default values in a \texttt{*.properties} file.
In this properties file, use a suffix on the end of the PID (persistent
identifier) and then provide your settings. For example, the following
code uses the \texttt{-staging} suffix on the PID and creates a CXF
endpoint at the context path \texttt{/staging-ws}:
\texttt{com.liferay.portal.remote.cxf.common.configuration.CXFEndpointPublisherConfiguration-staging.properties}:
\begin{verbatim}
contextPath=/staging-ws
\end{verbatim}
As another example, the following code uses the suffix
\texttt{-stagingjaxws} on the PID and creates a SOAP extender at the
context path \texttt{/staging-ws}. This code also includes settings for
the configuration fields \texttt{jaxWsHandlerFilterStrings} and
\texttt{jaxWsServiceFilterStrings}:
\texttt{com.liferay.portal.remote.soap.extender.internal.configuration.SoapExtenderConfiguration-stagingjaxws.properties}:
\begin{verbatim}
contextPaths=/staging-ws
jaxWsHandlerFilterStrings=(staging.jax.ws.handler=true)
jaxWsServiceFilterStrings=(staging.jax.ws.service=true)
\end{verbatim}
You must then use these configuration fields in the configuration class.
For example, the \texttt{SoapExtenderConfiguration} interface below
contains the configuration fields \texttt{contextPaths},
\texttt{jaxWsHandlerFilterStrings}, and
\texttt{jaxWsServiceFilterStrings}:
\begin{verbatim}
@ExtendedObjectClassDefinition(
category = "foundation", factoryInstanceLabelAttribute = "contextPaths"
)
@Meta.OCD(
factory = true,
id = "com.liferay.portal.remote.soap.extender.internal.configuration.SoapExtenderConfiguration",
localization = "content/Language", name = "soap.extender.internal.configuration.name"
)
public interface SoapExtenderConfiguration {
@Meta.AD(required = false)
public String[] contextPaths();
@Meta.AD(name = "jax.ws.handler.filters", required = false)
public String[] jaxWsHandlerFilterStrings();
@Meta.AD(name = "jax.ws.service.filters", required = false)
public String[] jaxWsServiceFilterStrings();
@Meta.AD(name = "soap.descriptor.builder", required = false)
public String soapDescriptorBuilderFilter();
}
\end{verbatim}
Next, you'll learn how to publish JAX-WS web services.
\section{Publishing JAX-WS Web
Services}\label{publishing-jax-ws-web-services}
To publish JAX-WS web services via SOAP in a module, annotate the class
and its methods with standard JAX-WS annotations, and then register it
as a service in the OSGi framework. For example, the following class
uses the \texttt{@WebService} annotation for the class and
\texttt{@WebMethod} annotations for its methods. You must also set the
\texttt{jaxws} property to \texttt{true} in the OSGi \texttt{@Component}
annotation:
\begin{verbatim}
import javax.jws.WebMethod;
import javax.jws.WebService;
import org.osgi.service.component.annotations.Component;
@Component(
immediate = true, property = "jaxws=true", service = Calculator.class
)
@WebService
public class Calculator {
@WebMethod
public int divide(int a, int b) {
return a / b;
}
@WebMethod
public int multiply(int a, int b) {
return a * b;
}
@WebMethod
public int subtract(int a, int b) {
return a - b;
}
@WebMethod
public int sum(int a, int b) {
return a + b;
}
}
\end{verbatim}
You should also make sure that you include \texttt{org.osgi.core} and
\texttt{org.osgi.service.component.annotations} as dependencies to your
project.
\chapter{GraphQL APIs}\label{graphql-apis}
Liferay DXP exposes a full
\href{https://graphql.github.io/graphql-spec/June2018/}{GraphQL API}
implementation, and lets your apps fetch several entities from the same
request.
Here you'll learn how to navigate and consume Liferay DXP's GraphQL
APIs. Since GraphQL APIs are discoverable, you'll start with that.
\chapter{Get Started: Discover the
API}\label{get-started-discover-the-api}
To begin consuming the GraphQL APIs, you must first know where they are,
what operations you can invoke, and how to invoke them.
Because Liferay DXP's GraphQL APIs leverage the
\href{https://graphql.github.io/graphql-spec/June2018/}{official
specification}, you don't need a service catalog. You only need to know
the URL from which to discover the rest of the API.
Liferay DXP's GraphQL APIs are available here:
\begin{verbatim}
http://[host]:[port]/o/graphql
\end{verbatim}
For example, if you're running Liferay DXP locally on port
\texttt{8080}, the URL for discovering the GraphQL API is
\begin{verbatim}
http://localhost:8080/o/graphql
\end{verbatim}
To inspect the GraphQL endpoint, use a GraphQL client, such as
\href{https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeellpciglgpaodhkhmapeljopja?hl=en}{Altair}
(a Chrome extension) or
\href{https://github.com/graphql/graphiql}{GraphiQL}.
\begin{figure}
\centering
\includegraphics{./images/graphql-altair.png}
\caption{GraphQL APIs can be browsed in Altair.}
\end{figure}
You don't have to be authenticated to inspect the live documentation,
but you must to be able to make requests. There are several ways of
authenticating in GraphQL APIs (explained
\href{/docs/7-2/frameworks/-/knowledge_base/f/authenticated-requests}{here})
but the simplest way to test APIs locally is to use Basic
Authentication, setting an \texttt{Authorization} header in Altair
(first icon on the left). Remember that Basic Auth is a BASE64
transformation of \texttt{user}:\texttt{password}. This means it's
insecure, and should never be used in production.
Most tools that introspect the GraphQL schema can autocomplete your
query or fill all the fields in for you.
For a list of tools such as client generators, validators, and parsers
supporting GraphQL, see
\href{https://github.com/chentsulin/awesome-graphql}{Awesome GraphQL}.
Leveraging GraphQL provides standards support, extensive automatic
documentation, and industry-wide conventions.
\section{Unique endpoint and
versioning}\label{unique-endpoint-and-versioning}
In contrast with the REST APIs, where endpoints are deployed by suite
(headless-delivery, headless-admin-user\ldots), GraphQL APIs are
deployed under the same endpoint (/o/graphql). That way we can easily
add relationships between entities to leverage GraphQL's powerful
request characteristics.
Liferay DXP's GraphQL APIs also expose the latest published version of
all entities available. If several versions of the same entity are
deployed, only the latest one is exposed under the \texttt{/o/graphql}
endpoint (REST APIs use different endpoints for different versions).
This strategy follows GraphQL standards to avoid breaking versions by
marking deprecated fields and always adding properties to an entity.
\chapter{Get Started: Invoke a
Service}\label{get-started-invoke-a-service}
Once you know which API you want to call via the GraphQL-introspected
documentation, you can send a request using a POST body. For example,
suppose you want to retrieve all the blog entries from a Site. If you
consult the GraphQL documentation you can find this endpoint:
\begin{figure}
\centering
\includegraphics{./images/graphql-blog-postings.png}
\caption{GraphQL exposes a definition for BlogPostings.}
\end{figure}
If you add the full query with Altair/GraphiQL, you'll see a result like
this:
\begin{verbatim}
query{
blogPostings(filter: ______, page: ______, pageSize: ______, search: ______, siteId: ______, siteKey: ______, sort: ______){
items
page
pageSize
totalCount
}
}
\end{verbatim}
The only required parameter is \texttt{siteId} or \texttt{siteKey} (as
of 7.2 FP4), the ID, or the internal name (like \emph{guest}) of the
blog posting's Site. Internally, the \texttt{siteId} is a
\texttt{groupId} that you can retrieve from the database, a URL, or
Liferay DXP's UI via the Site Administration menu. For more information,
see
\href{/docs/7-2/frameworks/-/knowledge_base/f/how-to-get-site-id}{How to
get SiteId}.
A regular query would ignore optional parameters and return more
elements of the list, identified by the property \texttt{items}:
\begin{verbatim}
query {
blogPostings(siteKey: "guest") {
items {
alternativeHeadline
articleBody
creator {
name
}
dateCreated
dateModified
datePublished
description
encodingFormat
friendlyUrlPath
headline
id
keywords
numberOfComments
relatedContents {
title
}
siteId
taxonomyCategoryIds
viewableBy
}
page
pageSize
totalCount
}
}
\end{verbatim}
In GraphQL, you must list explicitly every field you want to return in
the request. Complex objects, like \texttt{items}, \texttt{creator}, or
\texttt{relatedContents} can not be returned fully: you must specify at
least one field (or none).
To execute the query, make a \texttt{POST} request with an
\texttt{Authentication} header and the query in a JSON object under the
key \texttt{query}. Don't forget to escape strings!
The following request gets the Site's blog postings by providing the
site key (\texttt{guest}):
\begin{verbatim}
curl -X "POST" "http://localhost:8080/o/graphql" \
-H 'Content-Type: text/plain; charset=utf-8' \
-u 'test@liferay.com:test' \
-d $'{
"query": "query { blogPostings(siteKey: \\"guest\\") { items { alternativeHeadline articleBody creator { id name } dateCreated dateModified datePublished description encodingFormat friendlyUrlPath headline id keywords numberOfComments relatedContents { title } siteId taxonomyCategoryIds viewableBy } page pageSize totalCount } }"
}'
\end{verbatim}
If you send this request to a Site that contains some Blog entries, the
response may look like this:
\begin{verbatim}
{
"data": {
"blogPostings": {
"items": [
{
"alternativeHeadline": "",
"articleBody": "Content
",
"creator": {
"id": 20124,
"name": "Test Test"
},
"dateCreated": "2019-10-29T17:48:03Z",
"dateModified": "2019-10-29T17:48:03Z",
"datePublished": "2019-10-29T17:47:00Z",
"description": "",
"encodingFormat": "text/html",
"friendlyUrlPath": "title",
"headline": "Title",
"id": 37644,
"keywords": [],
"numberOfComments": 0,
"relatedContents": [],
"siteId": 20118,
"taxonomyCategoryIds": null,
"viewableBy": null
}
],
"page": 1,
"pageSize": 20,
"totalCount": 1
}
}
}
\end{verbatim}
This response is a JSON object with information about the collection of
blogs. The attributes contain information about the resource (blogs, in
this case). Also, note that the results are paginated. The
\texttt{*page*} attributes refer to pages of results. Here's a
description of some common attributes:
\texttt{id}: Each item has an ID. You can use the ID to retrieve more
information about that item. For example, there are two \texttt{id}
attributes in the above response: one for the blog posting
(\texttt{37644}) and one for the blog post's creator (\texttt{20124}).
\texttt{page}: The current page's page number. The page in the above
response is \texttt{1}.
\texttt{pageSize}: The possible number of this resource's items to be
included in a single page. In the above response this is \texttt{20}.
\texttt{totalCount}: The total number of this resource's existing items
(independent of pagination). The above response lists the total number
of blog postings (\texttt{1}) in a Site.
To get information on a specific blog posting, send a POST request with
the \texttt{blogPostingId} to this query:
\begin{verbatim}
query {
blogPosting(blogPostingId: 37644) {
headline
id
}
}
\end{verbatim}
\section{GraphQL Clients}\label{graphql-clients}
The examples above show the GraphQL requests as cURL operations but you
can use any GraphQL clients available. We have made heavy use of
\href{https://www.apollographql.com/docs/}{Apollo}, the official React
client and the \href{https://github.com/vuejs/vue-apollo}{Vue
integration} without any issues.
\chapter{Making Authenticated
Requests}\label{making-authenticated-requests-1}
To make an authenticated request, you must authenticate as a specific
User.
There are three authentication mechanisms available when invoking web
APIs:
\textbf{Basic Authentication:} Sends the user credentials as an encoded
user name and password pair. This is the simplest authentication
protocol (available since HTTP/1.0), but should be used only for
development purposes, as it's insecure.
\textbf{OAuth 2.0:} In 7.0, you can use OAuth 2.0 for authorization. See
the \href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0}{OAuth 2.0
documentation} for more information.
\textbf{Cookie/Session authentication:} From inside the portal you can
do direct requests to the APIs by sending the session token.
First, you'll learn how to send requests with basic authentication.
\section{Basic Authentication}\label{basic-authentication-1}
Basic authentication requires that you send an HTTP
\texttt{Authorization} header containing the encoded user name and
password. You must first get that encoded value. To do so, you can use
\texttt{openssl} or a \texttt{Base64} encoder. Either way, you must
encode the \texttt{user:password} string. Here's an example of the
\texttt{openssl} command for encoding the \texttt{user:password} string
for a user \texttt{test@liferay.com} with the password \texttt{Liferay}:
\begin{verbatim}
openssl base64 <<< test@liferay.com:Liferay
\end{verbatim}
This returns the encoded value:
\begin{verbatim}
dGVzdEBsaWZlcmF5LmNvbTpMaWZlcmF5Cg==
\end{verbatim}
If you don't have \texttt{openssl} installed, try the \texttt{base64}
command:
\begin{verbatim}
base64 <<< test@liferay.com:Liferay
\end{verbatim}
\noindent\hrulefill
\textbf{Warning:} Encoding a string as shown here does not encrypt the
resulting string. The encoded string can easily be decoded by executing
\texttt{base64\ \textless{}\textless{}\textless{}\ the-encoded-string},
which returns the original string.
Anyone listening to your request could therefore decode the
\texttt{Authorization} header and reveal your user name and password. To
prevent this, ensure that all communication is made through HTTPS, which
encrypts the entire message (including headers).
\noindent\hrulefill
Use the encoded value for the HTTP Authorization header when sending the
request:
\begin{verbatim}
curl -H "Authorization: Basic dGVzdEBsaWZlcmF5LmNvbTpMaWZlcmF5Cg==" http://localhost:8080/o/graphql ...
\end{verbatim}
The response contains data instead of the 403 error that an
unauthenticated request receives. For more information on the response's
structure, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}.
\section{OAuth 2.0 Authorization}\label{oauth-2.0-authorization-1}
7.0 supports authorization via OAuth 2.0, which is a token-based
authorization mechanism. For more details, see
\href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0}{Liferay DXP's OAuth
2.0 documentation}. The following sections show you how to use OAuth 2.0
to authenticate web API requests.
\section{Obtaining the OAuth 2.0
Token}\label{obtaining-the-oauth-2.0-token-1}
Before using OAuth 2.0 to invoke a web API, you must register your
application (your web API's consumer) as an authorized OAuth client. To
do this, follow the instructions in
\href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0\#creating-an-application}{Creating
an Application}. When creating the application, fill in the form as
follows:
\textbf{Application Name:} Your application's name.
\textbf{Client Profile:} Headless Server.
\textbf{Allowed Authorization Types:} Check \emph{Client Credentials}.
After clicking \emph{Save} to finish creating the application, write
down the Client ID and Client Secret values that appear at the top of
the form.
Next, you must get an OAuth 2.0 access token. To do this, see
\href{/docs/7-2/deploy/-/knowledge_base/d/authorizing-account-access-with-oauth2}{Authorizing
Account Access with OAuth 2}.
\section{Invoking the Service with an OAuth 2.0
Token}\label{invoking-the-service-with-an-oauth-2.0-token-1}
Once you have a valid OAuth 2.0 token, include it in the request's
\texttt{Authorization} header, specifying that the authentication type
is a \href{https://tools.ietf.org/html/rfc6750}{bearer token}:
\begin{verbatim}
curl -H "Authorization: Bearer d5571ff781dc555415c478872f0755c773fa159" http://localhost:8080/o/graphql
\end{verbatim}
The response contains the resources that the authenticated user has
permission to access, just like the response from Basic authentication.
The request could be prevented depending on the scopes defined. If POST
a GraphQL query and there is scope disabling all request except
\texttt{GET}, you see a 403.
\section{Using Cookie Authentication or doing a request from the
portal}\label{using-cookie-authentication-or-doing-a-request-from-the-portal}
You can call the GraphQL APIs using the existing session from outside
the Liferay DXP by passing the session identifier (the cookie reference)
and the Liferay Auth Token (a CSRF---Cross-Site Request
Forgery---token).
To make an unauthenticated request from outside the Liferay DXP you must
provide the \texttt{Cookie} identifier in the header:
\begin{verbatim}
curl -H 'Cookie: JSESSIONID=27D7C95648D7CDBE3347601FC4543F5D'
\end{verbatim}
You must also provide the CSRF token by passing it as a query parameter
called \texttt{p\_auth} or by adding the URL to the whitelist of CSRF
allowed URLs or disabling CSRF checks altogether with the
\texttt{auth.verifier.auth.verifier.PortalSessionAuthVerifier.check.csrf.token}
property (application level).
Here's a sample cURL request with the cookie and CSRF token:
\begin{verbatim}
curl -H 'Cookie: JSESSIONID=27D7C95648D7CDBE3347601FC4543F5D' http://localhost:8080/o/graphql?p_auth=O4dCU1Mj
\end{verbatim}
To do an unauthenticated request from inside the Liferay DXP, from
JavaScript code or a Java method, you don't need the session identifier.
You must only provide the CRSF token or add the API to the whitelist of
CSRF allowed URLs.
\section{Making Unauthenticated
Requests}\label{making-unauthenticated-requests-1}
Unauthenticated requests are disabled by default in Liferay DXP's
GraphQL APIs. As all GraphQL APIs share the same endpoint, you cannot
have the same level of granularity with Service Access Policies as in
REST APIs. For that reason, we do not recommend disabling the security
of the GraphQL APIs.
\section{Related Topics}\label{related-topics-136}
\href{/docs/7-2/frameworks/-/knowledge_base/f/get-started-invoke-a-service}{Get
Started: Invoke a Service}
\href{/docs/7-2/frameworks/-/knowledge_base/f/working-with-collections-of-data}{Working
with Collections of Data}
\chapter{Working with Collections of
Data}\label{working-with-collections-of-data-1}
Collection resources are common in Liferay DXP web APIs. If you followed
along with the previous examples that sent requests to the portal's
\texttt{blog-postings} resource URL, you've already seen collections in
action: the \texttt{BlogPosting} resource is a collection.
Here, you'll learn more detailed information about working with
collection resources. But first, you should learn about collection
pagination.
\section{Pagination}\label{pagination-2}
A small collection can be transmitted in a single response without
difficulty. Transmitting a large collection all at once, however, can
consume too much bandwidth, time, and memory. It can also overwhelm the
user with too much data.
It's therefore best to get and display the elements of a large
collection in discrete chunks, or pages.
Liferay DXP's GraphQL APIs return paginated collections by default. The
following attributes in the responses also contain the information
needed to navigate between those pages:
\texttt{totalCount}: The total number of this resource's items.
\texttt{pageSize}: The number of this resource's items to be included in
this response.
\texttt{page}: The current page's number.
\texttt{items}: The collection elements present on this page. Each
element also contains the data of the object it represents, so there's
no need for additional requests for individual elements.
\texttt{id}: Each item's identifier. You can use this, if necessary, to
get more information on a specific item.
The attributes \texttt{page} and \texttt{pageSize} allow client
applications to navigate through the results. For example, such a client
could send a request for a specific page.
This example gets the second page (\texttt{page:2}) of blog postings
that exist on the content set with the ID \texttt{42345}:
\begin{verbatim}
query {
contentSetContentSetElements(contentSetId: 42345, page: 2) {
items {
id
title
content {
... on BlogPosting {
headline
}
}
}
page
pageSize
totalCount
}
}
\end{verbatim}
\chapter{Mutations}\label{mutations}
The GraphQL spec differentiates between retrieve operations
(\texttt{query}) and create/update/delete operations
(\texttt{mutations}).
\begin{figure}
\centering
\includegraphics{./images/graphql-mutation.png}
\caption{The GraphQL Mutations list for Blog postings shows the possible
operations.}
\end{figure}
To perform a mutation, do a \texttt{POST} request as you did with
\texttt{query} operations, using cURL, a REST client, or a GraphQL
Client like Apollo. The only difference is that create/update
\texttt{mutations} require an Input type, a JSON object to create or
update the content.
A create \texttt{mutation} to insert a new blog posting looks like this:
\begin{verbatim}
mutation {
createSiteBlogPosting(
blogPosting: {
headline: "New GraphQL APIs!"
articleBody: "WoW! This is cool!"
}
siteKey: "guest"
) {
id
headline
}
}
\end{verbatim}
Auto-complete also works as expected filling the \texttt{blogPosting}
object. Here's a cURL request to create the same entry:
\begin{verbatim}
curl 'http://localhost:8080/o/graphql' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Authorization: Basic dGVzdEBsaWZlcmF5LmNvbTp0ZXN0' --data-binary '{"query":"mutation {createSiteBlogPosting(blogPosting: {headline: \"New GraphQL APIs!\" articleBody: \"WoW! This is cool!\"} siteKey: \"guest\") {id headline}}","variables":{}}'
\end{verbatim}
\chapter{Fragments and Node Patterns}\label{fragments-and-node-patterns}
Liferay DXP's GraphQL APIs also supports
\href{https://graphql.org/learn/queries/\#fragments}{GraphQL fragments},
reusable sets of fields that are needed in different requests. A special
type of fragments are
\href{https://graphql.org/learn/queries/\#inline-fragments}{inline
fragments}, which access the underlying concrete type when querying
generic types or interfaces.
You'll use inline fragments to query objects that inherit from a common
interface, like the kind of objects returned from a \texttt{ContentSet}.
\texttt{ContentSet}s allow defining lists of assets that comply with a
set of rules, a segment, or are manually selected. \texttt{ContentSet}s
can return any type of asset. This makes them a perfect fit for inline
fragments.
Here's an example of GraphQL querying \texttt{ContentSet}s:
\begin{verbatim}
query {
contentSetContentSetElements(contentSetId: 42345) {
items {
id
title
content {
... on BlogPosting {
headline
}
... on StructuredContent {
relatedContents {
id
title
}
}
}
}
page
pageSize
totalCount
}
}
\end{verbatim}
This query returns a set of objects, each of a different type.
\section{Node pattern}\label{node-pattern}
\texttt{graphQLNode} is a special query that leverages the power of
inline fragments. This query accepts a \texttt{dataType} and an ID and
returns any kind of entity that has a query of the type
\texttt{\{dataType\}} and receives an \texttt{id} as a parameter. Inline
fragments can specify the fields you want to return in this special
case:
\begin{verbatim}
query{
graphQLNode(dataType: ______, id: ______){
id
... on BlogPosting {
headline
}
}
}
\end{verbatim}
You can also use \texttt{graphQLNode} as a field in entities that
contain \texttt{contentType} and \texttt{id} properties. Those entities
have a generated property called \texttt{GraphQLNode} that can return
any type, queried by using inline fragments. A common use case is
returning an asset linked as \texttt{relatedContents} (asset links).
\chapter{Language Negotiation}\label{language-negotiation-1}
The same mechanism for requesting content in another language in the
headless REST APIs is used in GraphQL.
APIs available in different languages return the options in a block
called \texttt{availableLanguages}. For example, this block lists U.S.
English (\texttt{en-US}) and Spain/Castilian Spanish (\texttt{es-ES}):
\begin{verbatim}
{
"availableLanguages": [
"en-US",
"es-ES"
],
"contentFields": [
{
"dataType": "html",
"name": "content",
"repeatable": false,
"value": {
"data": "The main reason is because Headless APIs have been designed with real use cases in mind...
"
}
}
],
"contentStructureId": 36801,
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T10:29:40Z",
"dateModified": "2019-04-22T10:30:31Z",
"datePublished": "2019-04-22T10:28:00Z",
"friendlyUrlPath": "why-headless-apis-are-better-than-json-ws-services-",
"id": 59325,
"key": "59323",
"numberOfComments": 0,
"renderedContents": [
{
"renderedContentURL": "http://localhost:8080/o/headless-delivery/v1.0/structured-contents/59325/rendered-content/36804",
"templateName": "Basic Web Content"
}
],
"siteId": 20124,
"title": "Why Headless APIs are better than JSON-WS services?",
"uuid": "e1c4c152-e47c-313f-2d16-2ee4eba5cd26"
}
\end{verbatim}
To request the content in another language, specify your desired locale
in the request's \texttt{Accept-Language} header:
\begin{verbatim}
curl "http://localhost:8080/o/graphql" -H 'Accept-Language: es-ES' -u 'test@liferay.com:test' ...
\end{verbatim}
\begin{verbatim}
{
"availableLanguages": [
"en-US",
"es-ES"
],
"contentFields": [
{
"dataType": "html",
"name": "content",
"repeatable": false,
"value": {
"data": "La principal razón es porque las APIs Headless se han diseñado pensando en casos de uso reales...
"
}
}
],
"contentStructureId": 36801,
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T10:29:40Z",
"dateModified": "2019-04-22T10:30:31Z",
"datePublished": "2019-04-22T10:28:00Z",
"friendlyUrlPath": "%C2%BFpor-qu%C3%A9-las-apis-headless-son-mejores-que-json-ws-",
"id": 59325,
"key": "59323",
"numberOfComments": 0,
"renderedContents": [
{
"renderedContentURL": "http://localhost:8080/o/headless-delivery/v1.0/structured-contents/59325/rendered-content/36804",
"templateName": "Contenido web básico"
}
],
"siteId": 20124,
"title": "¿Por qué las APIs Headless son mejores que JSON-WS?",
"uuid": "e1c4c152-e47c-313f-2d16-2ee4eba5cd26"
}
\end{verbatim}
\section{Creating Content with Different
Languages}\label{creating-content-with-different-languages-1}
By default, when sending a mutation request, the
\texttt{Accept-Language} header is used as the content's language. There
is one exception, however. Some entities require the first request to be
in the Site's default language. In such cases, the first request for a
different language results in an error.
After creating a new resource, a new request in a different language
adds that translation.
\chapter{Filter, Sort, and Search}\label{filter-sort-and-search-1}
You can use Liferay DXP's headless GraphQL APIs to search for the
content you want. You can also sort and filter content.
\section{Filter}\label{filter-1}
It's often useful to filter large collections for the exact data that
you need. Not all collections, however, allow filtering. The ones that
support it contain the optional parameter \texttt{filter}. To filter a
collection based on the value of one or more fields, use the
\texttt{filter} parameter following a subset of the
\href{https://www.odata.org}{OData} standard.
Filtering mainly applies to fields indexed as keywords in Liferay DXP's
search. To find content by terms contained in fields indexed as text,
you should instead use \hyperref[search]{search}.
\section{Comparison Operators}\label{comparison-operators-1}
\noindent\hrulefill
Operator \textbar{} Description \textbar{} Example \textbar{}
\texttt{eq} \textbar{} Equal \textbar{}
\texttt{addressLocality\ eq\ \textquotesingle{}Redmond\textquotesingle{}}
\textbar{} \textbar{} Equal null \textbar{}
\texttt{addressLocality\ eq\ null} \textbar{} \texttt{ne} \textbar{} Not
equal \textbar{}
\texttt{addressLocality\ ne\ \textquotesingle{}London\textquotesingle{}}
\textbar{} \textbar{} Not null \textbar{}
\texttt{addressLocality\ ne\ null} \textbar{} \texttt{gt} \textbar{}
Greater than \textbar{} \texttt{price\ gt\ 20} \textbar{} \texttt{ge}
\textbar{} Greater than or equal \textbar{} \texttt{price\ ge\ 10}
\textbar{} \texttt{lt} \textbar{} Less than \textbar{}
\texttt{dateCreated\ lt\ 2018-02-13T12:33:12Z} \textbar{} \texttt{le}
\textbar{} Less than or equal \textbar{}
\texttt{dateCreated\ le\ 2012-05-29T09:13:28Z} \textbar{}
\texttt{startswith} \textbar{} Starts with \textbar{}
\texttt{startswith(addressLocality,\ \textquotesingle{}Lond\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{Logical Operators}\label{logical-operators-1}
\noindent\hrulefill
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Operator & Description & Example \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{and} & Logical and &
\texttt{price\ le\ 200\ and\ price\ gt\ 3.5} \\
\texttt{or} & Logical or &
\texttt{price\ le\ 3.5\ or\ price\ gt\ 200} \\
\texttt{not} & Logical not & \texttt{not\ (price\ le\ 3.5)} \\
\end{longtable}
\noindent\hrulefill
Note that the \texttt{not} operator requires a trailing space.
\section{Grouping Operators}\label{grouping-operators-1}
\noindent\hrulefill
Operator \textbar{} Description \textbar{} Example \textbar{}
\texttt{(\ )} \textbar{} Precedence grouping \textbar{}
\texttt{(price\ eq\ 5)\ or\ (addressLocality\ eq\ \textquotesingle{}London\textquotesingle{})}
\textbar{}
\noindent\hrulefill
\section{String Functions}\label{string-functions-1}
\noindent\hrulefill
\begin{longtable}[]{@{}lll@{}}
\toprule\noalign{}
Function & Description & Example \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{contains} & Contains &
\texttt{contains(title,\textquotesingle{}edmon\textquotesingle{})} \\
\end{longtable}
\noindent\hrulefill
\section{Lambda Operators}\label{lambda-operators-1}
Lambda operators evaluate a boolean expression on a collection. They
must be prepended with a navigation path that identifies a collection.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2206}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.1618}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.6176}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Lambda Operator
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Example
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{any} & Any &
\texttt{keywords/any(k:contains(k,\textquotesingle{}substring1\textquotesingle{}))} \\
\end{longtable}
\noindent\hrulefill
The \texttt{any} operator applies a boolean expression to each
collection element and evaluates to \texttt{true} if the expression is
true for any element.
\section{Escaping in Queries}\label{escaping-in-queries-1}
You can escape a single quote in a value by escaping it with a
backslash. For example, to filter for a blog posting whose headline is
\texttt{New\ Headless\ APIs}, send this filter string to the filter
parameter.
\begin{verbatim}
filter: \\"headline eq \'Title\'\\"
\end{verbatim}
Here's an example of the full request:
\begin{verbatim}
curl -X "POST" "http://localhost:8080/o/graphql" \
-H 'Content-Type: text/plain; charset=utf-8' \
-H 'Cookie: COOKIE_SUPPORT=true; GUEST_LANGUAGE_ID=en_US; JSESSIONID=EFEEC1617529C7C85E8CCCE510B0F6CF' \
-u 'test@liferay.com:test' \
-d $'{
"query": "query { blogPostings(siteKey: \\"guest\\", filter: \\"headline eq \'Title\'\\") { items {headline} page pageSize totalCount } }"
}'
\end{verbatim}
And here's a possible response:
\begin{verbatim}
{
"items": [
{
"alternativeHeadline": "The power of OpenAPI & Liferay",
"articleBody": "We are happy to announce...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T07:04:47Z",
"dateModified": "2019-04-22T07:04:51Z",
"datePublished": "2019-04-22T07:02:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "new-headless-apis",
"headline": "New Headless APIs",
"id": 59301,
"numberOfComments": 0,
"siteId": 20124
}
],
"page": 1,
"pageSize": 20,
"totalCount": 1
}
\end{verbatim}
\section{Filtering in Structured Content Fields
(ContentField)}\label{filtering-in-structured-content-fields-contentfield-1}
To filter for a \texttt{ContentField} value (dynamic values created by
the end user), you must use the paths that are scoped to an individual
\texttt{ContentStructure}.
Find the ID of the \texttt{ContentStructure} and use it in place of
\texttt{\{contentStructureId\}} in this query:
\begin{verbatim}
"contentStructureStructuredContents"
\end{verbatim}
\section{Search}\label{search-3}
You can search large collections with keywords. Use search when you want
results from any field, rather than specific ones. To perform a search,
use the optional parameter \texttt{search} followed by the search terms.
For example, this request searches for all the \texttt{BlogEntry} fields
containing Title:
\begin{verbatim}
curl -X "POST" "http://localhost:8080/o/graphql" \
-H 'Content-Type: text/plain; charset=utf-8' \
-u 'test@liferay.com:test' \
-d $'{
"query": "query { blogPostings(siteKey: \\"guest\\", search: \\"Title\\") { items {headline} page pageSize totalCount } }"
}'
\end{verbatim}
\begin{verbatim}
{
"items": [
{
"alternativeHeadline": "How to work with OAuth",
"articleBody": "To configure OAuth...
",
"creator": {
"familyName": "Test",
"givenName": "Test",
"id": 20130,
"name": "Test Test",
"profileURL": "/web/test"
},
"dateCreated": "2019-04-22T09:35:09Z",
"dateModified": "2019-04-22T09:35:09Z",
"datePublished": "2019-04-22T09:34:00Z",
"encodingFormat": "text/html",
"friendlyUrlPath": "authenticated-requests",
"headline": "Authenticated requests",
"id": 59309,
"numberOfComments": 0,
"siteId": 20124
}
],
"page": 1,
"pageSize": 20,
"totalCount": 1
}
\end{verbatim}
\section{Sorting}\label{sorting-1}
Collection results can be sorted. Note, however, that not all
collections allow sorting. The ones that support it contain the optional
parameter \texttt{\{lb\}?sort\{rb\}} in their GraphQL definition.
To get sorted collection results, append
\texttt{sort:\textbackslash{}"\textless{}param-name\textgreater{}\textbackslash{}"}
to the request URL. For example, appending
\texttt{sort:\textbackslash{}"title\textbackslash{}"} to the request URL
sorts the results by title.
The default sort order is ascending (0-1, A-Z). To perform a descending
sort, append \texttt{:desc} to the parameter name. For example, to
perform a descending sort by title, append
\texttt{sort:\textbackslash{}"title:desc\textbackslash{}"} to the
request URL.
To sort by more than one parameter, separate the parameter names by
commas and put them in order of priority. For example, to sort first by
title and then by creation date, append
\texttt{sort:\textbackslash{}"title,dateCreated\textbackslash{}"} to the
request URL.
To specify a descending sort for only one parameter, you must explicitly
specify ascending sort order (\texttt{:asc}) for the other parameters:
\begin{verbatim}
sort:\"headline:desc,dateCreated:asc\"
\end{verbatim}
\section{Flatten}\label{flatten-1}
The \texttt{flatten} query parameter returns all resources and
disregards folders or other hierarchical classifications. Collection
GraphQL specifications define if \texttt{flatten} is available. Its
default value is \texttt{false}, so a document query to the root folder
returns only the documents in that folder.
With \texttt{flatten} set to \texttt{true}, the same query returns
documents in any subfolders, regardless of how deeply those folders are
nested. Setting \texttt{flatten} set to \texttt{true} and querying for
documents in a Site's root folder returns all the documents in the Site.
\chapter{Multipart Requests}\label{multipart-requests-1}
Several mutations accept a binary file via a multipart request. For
example, the definition for posting a file to a \texttt{DocumentFolder}
specifies a multipart request, \texttt{Upload} type in GraphQL:
\begin{figure}
\centering
\includegraphics{./images/graphql-mutation-upload.png}
\caption{Create Document accepts a \texttt{multipartBody}.}
\end{figure}
The GraphQL specification doesn't support natively multipart uploads,
but an
\href{https://github.com/jaydenseric/graphql-multipart-request-spec}{extension}
contributed by the community covers that use case.
Liferay's implementation includes that extension and allows uploading
files.
Multipart support in GraphQL is disabled by default. To enable it, add
the configuration to upload multipart files in the Liferay application's
\texttt{web.xml} file:
\begin{verbatim}
Module Framework Servlet
com.liferay.portal.module.framework.ModuleFrameworkServletAdapter
1
true
/tmp
20848820
418018841
1048576
\end{verbatim}
To test, use the Altair configuration to upload files. Use the selector
to upload one or multiple files and define a variable in the query.
\begin{figure}
\centering
\includegraphics{./images/graphql-mutation-upload-altair.png}
\caption{Creating a Document in Altair is easy with the selector.}
\end{figure}
\begin{verbatim}
mutation($file: [Upload]) {
createSiteDocument(multipartBody: $file, siteKey: "guest") {
id
title
}
}
\end{verbatim}
The variable above is \texttt{file} because there's only one. If you
wanted to upload several files, name the variable \texttt{\$files} and
each file should have a numeric sequence: \texttt{files.0},
\texttt{files.1}, \texttt{files.2}, etc.
All multipart APIs allow sending a JSON file containing the file's
metadata (title, description, etc.). That parameter should be the second
file uploaded (defined using the \texttt{file.0}, \texttt{file.1}
syntax).
For example,
\begin{verbatim}
document={\"title\": \"Alternative name\"}"
\end{verbatim}
And here's the response:
\begin{verbatim}
{
"data": {
"createSiteDocument": {
"id": 37701,
"title": "99-rest-generator.markdown"
}
}
}
\end{verbatim}
The cURL request is slightly different (Altair fills out the variables):
\begin{verbatim}
curl 'http://localhost:8080/o/graphql' -H 'Authorization: Basic dGVzdEBsaWZlcmF5LmNvbTp0ZXN0' \
-F operations='{"query":"mutation($files: [Upload]) {createSiteDocument(multipartBody: $files, siteId: 20122) {id}}","variables": { "files": [null] } }' \
-F map='{ "0": ["variables.files.0"]}' \
-F 0=@"99-rest-generator.markdown"
\end{verbatim}
\chapter{Using GraphQL APIs}\label{using-graphql-apis}
Liferay DXP's GraphQL APIs are independent of clients and can be used
with any GraphQL client you want. The only usual requirements are
setting up the \texttt{Authentication} header using OAuth, Cookie,
Basic, etc.
For JavaScript applications, we recommend using
\href{https://www.apollographql.com/}{Apollo Client} or
\href{https://github.com/prisma-labs/graphql-request}{graphql-request},
like this:
\begin{verbatim}
const { GraphQLClient } = require('graphql-request');
const graphQLClient = new GraphQLClient('http://localhost:8080/o/graphql', {
headers: {
authorization: `Basic ${AUTHORIZATION_TOKEN}`
}
});
const getDestinationsQuery = ` {
destinations: contentSetContentSetElements(contentSetId: ${DESTINATION_CONTENTSET_ID}) {
items {
id
title
}
page
pageSize
totalCount
}
}`;
...
const response = await graphQLClient.request(getDestinationsQuery);
\end{verbatim}
Here are several examples of JavaScript applications using GraphQL APIs:
\begin{itemize}
\tightlist
\item
\href{https://github.com/dgomezg/liferay-frontend-samples/blob/master/lifeair-alexa-skill/}{Alexa
skill using GraphQL APIs}
\item
\href{https://github.com/dgomezg/liferay-frontend-samples/tree/master/lifeair-react-showroom}{React
application using Apollo Client for React}
\item
\href{https://github.com/dgomezg/liferay-frontend-samples/tree/master/lifeair-vue-showroom}{Vue
application using Apollo Client for Vue}
\end{itemize}
\chapter{REST Builder}\label{rest-builder}
REST Builder is Liferay DXP's tool to build REST and GraphQL APIs. It's
based on OpenAPI, following an
\href{/docs/7-2/frameworks/-/knowledge_base/f/headless-rest-apis}{API-first
approach}.
Using REST Builder takes only three steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Write the OpenAPI profile.
\item
Use REST builder to generate the scaffolding.
\item
Fill out the generated classes with your logic.
\end{enumerate}
A good overview of the process is detailed
\href{https://help.liferay.com/hc/es/articles/360028748872-Generating-APIs-with-REST-Builder}{here}.
We'll see each step in detail but first, let's talk about why we want to
use REST Builder.
\section{Why we should use REST
Builder}\label{why-we-should-use-rest-builder}
There are several reasons to prefer REST Builder over rolling our own
\href{https://help.liferay.com/hc/en-us/articles/360031902292-JAX-RS}{JAX-RS
services}. Some of them are the following:
\begin{itemize}
\tightlist
\item
Development speed: you avoid writing JAX-RS annotations, converters,
adding support for multipart or layers to organize your code.
Everything is generated.
\item
API scaffolding: pagination, filtering, searching, JSON writers, XML
generation, even unit, and integration tests are generated.
\item
GraphQL support out of the box: write your REST API and get a GraphQL
endpoint for free.
\item
Integration with Liferay's authentication pipelines: Basic, OAuth,
Cookie, CORS handling. You don't have to search manually for the user
or the company; everything is already there.
\item
JSON \& XML support: APIs return whichever format the consumer
prefers.
\item
Consistency: all APIs follow the same rules and conventions, enforced
by REST builder.
\end{itemize}
\chapter{How to install REST Builder}\label{how-to-install-rest-builder}
Use the
\href{https://portal.liferay.dev/docs/7-2/reference/-/knowledge_base/r/rest-builder-gradle-plugin}{Gradle
plugin} to install REST builder by adding this gradle configuration to
your project:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.rest.builder", version: "1.0.21"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.portal.tools.rest.builder"
\end{verbatim}
To use it, run \texttt{gradlew\ buildREST}. Note that your Gradle
wrapper may not be in your app's project directory, so you may need to
use \href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade} to
locate it:
\begin{verbatim}
blade gw buildREST
\end{verbatim}
If you want to use a specific version of REST builder, you can specify
it explicitly:
\begin{verbatim}
dependencies {
restBuilder group: "com.liferay", name: "com.liferay.portal.tools.rest.builder", version: "1.0.30"
}
\end{verbatim}
\chapter{REST Builder \& OpenAPI}\label{rest-builder-openapi}
REST Builder is based on
\href{https://swagger.io/specification/}{OpenAPI}, and its philosophy is
``OpenAPI first'': first you write the profile and then you use it as
the base of your implementation.
But first you must create a project with two empty bundles (a Blade
template will follow soon). The bundles (-api and -impl) should have the
files you are already used to: a \texttt{build.gradle} and a
\texttt{bnd.bnd}. The novelty is two YAML files, a configuration file
(\texttt{rest-config.yaml}) and the OpenAPI profile
(\texttt{rest-openapi.yaml}). An example project is
\href{https://github.com/nhpatt/liferay-devcon-appointment}{here}.
Let's see the configuration file in detail. In the root of the -impl
project we have to create a YAML file to specify paths and the basic
configuration of our new API. A sample implementation would be:
\begin{verbatim}
apiDir: "../headless-test-api/src/main/java"
apiPackagePath: "com.liferay.headless.test"
application:
baseURI: "/headless-test"
className: "HeadlessTestApplication"
name: "Liferay.Headless.Test"
author: "Javier Gamarra"
\end{verbatim}
This file specifies the path of the -api bundle, the java package that
we will use across all the bundles and the information of the JAX-RS
application: the path of our application, the name of the class and the
JAX-RS name of our API.
I've skipped two advanced features, generating a client and automated
tests, will see them later.
Just one step left, writing our OpenAPI profile.
\section{OpenAPI profile}\label{openapi-profile}
The OpenAPI profile will be the source of all our APIs, in this file, we
will add the paths and entities of our API. First, we'll create a
\href{https://en.wikipedia.org/wiki/YAML}{YAML} file called
rest-openapi.yaml. Writing YAML files is tricky so we recommend using
the \href{http://editor.swagger.io/}{swagger editor} to do it, which
validates the YAML file against YAML syntax and the
\href{https://github.com/OAI/OpenAPI-Specification}{OpenAPI
specification}.
A simple OpenAPI profile that retrieves a fictitious entity might look
like this:
\begin{verbatim}
components:
schemas:
Entity:
description: A very simple entity
properties:
name:
description: The entity name.
type: string
id:
description: The entity ID.
type: integer
type: object
info:
description: ""
title: "My API"
version: v1.0
openapi: 3.0.1
paths:
"/entities/{entityId}":
get:
parameters:
- in: path
name: entityId
required: true
schema:
type: integer
responses:
200:
content:
application/json:
schema:
$ref: "#/components/schemas/Entity"
description: ""
tags: ["Entity"]
\end{verbatim}
All OpenAPI profiles have three different sections: components, info,
and paths. The easiest one is the information block. It contains the
OpenAPI version, the title and the version of your API:
\begin{verbatim}
info:
description: ""
title: "My API"
version: v1.0
openapi: 3.0.1
\end{verbatim}
Indentations should be spaces. The
\href{http://editor.swagger.io/}{swagger editor} helps with formatting.
The components section specifies the schemas/entities to return or
accept on your APIs. In this case, you define a schema called
\emph{Entity} that has two string fields: a name and an id.
\begin{verbatim}
components:
schemas:
Entity:
description: A very simple entity
properties:
name:
description: The entity name.
type: string
id:
description: The entity ID.
type: integer
type: object
\end{verbatim}
The OpenAPI specification defines
\href{https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md\#schemaObject}{many
types and fields} you can use in your schemas.
The other common type is \texttt{\$ref}, a
\href{https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md\#referenceObject}{reference
type} that allows you to refer to an existing type like this:
\begin{verbatim}
$ref: '#/components/schemas/Entity'
\end{verbatim}
The last block, called paths, defines the URLs that you'll expose in
your APIs, with the type of HTTP verbs, list of parameters, status
codes, etc.
\begin{verbatim}
paths:
"/entities/{entityId}":
get:
parameters:
- in: path
name: entityId
required: true
schema:
type: integer
responses:
200:
content:
application/json:
schema:
$ref: "#/components/schemas/Entity"
description: ""
tags: ["Entity"]
\end{verbatim}
The pattern above, \texttt{"/entities/\{entity\}"}, follows a common
pattern in REST APIs. This is the endpoint that retrieves one element,
\texttt{"/entities"}. It returns a list of elements, and a POST request
creates one.
For every path, it is mandatory to add a tag that points to an existing
schema to indicate where to generate your code. REST Builder creates a
method inside the class \texttt{{[}TAG{]}ResourceImpl.java}.
\section{Generation}\label{generation}
Once you've written your OpenAPI configuration and profile, it's time to
generate your scaffolding for REST and GraphQL.
In the -impl or in the root module folder, execute this command:
\begin{verbatim}
gw buildREST
\end{verbatim}
You can use \texttt{gw\ bR} if you want to save a few keystrokes.
If everything's indented properly and the OpenAPI profile validates,
REST Builder generates your JAX-RS resources and the GraphQL endpoint.
Next, you'll see what has been generated and how to implement our
business logic.
\section{Examples}\label{examples}
Here's a complete example that defines all CRUD operations in OpenAPI.
\section{GET Collection}\label{get-collection}
\begin{verbatim}
paths:
"/entities":
get:
responses:
200:
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Entity"
type: array
description: ""
tags: ["Entity"]
\end{verbatim}
\section{DELETE}\label{delete}
\begin{verbatim}
paths:
"/entities/{entityId}":
delete:
parameters:
- in: path
name: entityId
required: true
schema:
type: integer
responses:
204:
content:
application/json: {}
description: ""
tags: ["Entity"]
\end{verbatim}
\section{POST}\label{post}
\begin{verbatim}
paths:
"/entities":
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Entity"
responses:
200:
content:
application/json:
schema:
$ref: "#/components/schemas/Entity"
description: ""
tags: ["Entity"]
\end{verbatim}
\section{PUT}\label{put}
\begin{verbatim}
paths:
"/entities/{entityId}":
put:
parameters:
- in: path
name: entityId
required: true
schema:
type: integer
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Entity"
responses:
200:
content:
application/json:
schema:
$ref: "#/components/schemas/Entity"
description: ""
tags: ["Entity"]
\end{verbatim}
\section{Summary}\label{summary}
There are more examples showcasing all the supported OpenAPI syntax
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/headless/headless-delivery/headless-delivery-impl/rest-openapi.yaml}{here}
and
\href{https://github.com/liferay/liferay-portal/blob/master/modules/apps/headless/headless-admin-taxonomy/headless-admin-taxonomy-impl/rest-openapi.yaml}{here}.
Your next step is to
\href{/docs/7-2/frameworks/-/knowledge_base/f/rest-builder-develop}{create
your API}.
\chapter{Developing an API with REST
Builder}\label{developing-an-api-with-rest-builder}
After executing \texttt{gw\ buildREST}, you have two modules:
\emph{headless-test-api} and \emph{headless-test-impl}.
\begin{itemize}
\item
Headless Test API contains the interfaces for your resources and the
POJOs of your schemas.
\item
Headless Test Impl contains your implementation and the JAX-RS
application.
\end{itemize}
Your generated \texttt{EntityResource} looks like this:
\begin{verbatim}
public interface EntityResource {
public Page getEntitiesPage() throws Exception;
public Entity postEntity(Entity entity) throws Exception;
public void deleteEntity(Integer entityId) throws Exception;
public Entity getEntity(Integer entityId) throws Exception;
public Entity putEntity(Integer entityId, Entity entity) throws Exception;
//Context methods
public default void setContextAcceptLanguage(AcceptLanguage contextAcceptLanguage) {}
public void setContextCompany(Company contextCompany);
public default void setContextHttpServletRequest(HttpServletRequest contextHttpServletRequest) {}
public default void setContextHttpServletResponse(HttpServletResponse contextHttpServletResponse) {}
public default void setContextUriInfo(UriInfo contextUriInfo) {}
public void setContextUser(User contextUser);
}
\end{verbatim}
These are generated methods you defined in the OpenAPI profile (the full
set as displayed in the examples).
REST builder also generates two implementation files, a base class, with
all the JAX-RS, GraphQL and OpenAPI annotations and an empty
implementation, \texttt{EntityResourceImpl}:
\begin{verbatim}
@Component(
properties = "OSGI-INF/liferay/rest/v1_0/entity.properties",
scope = ServiceScope.PROTOTYPE, service = EntityResource.class
)
public class EntityResourceImpl extends BaseEntityResourceImpl {
}
\end{verbatim}
This is where you implement new methods, by overriding the base class
implementation and returning your code. For example, here's a prototype
implementation storing entities in a Map:
\begin{verbatim}
Map entities = new HashMap<>();
@Override
public Entity getEntity(Integer entityId) throws Exception {
return entities.get(entityId);
}
@Override
public Page getEntitiesPage() throws Exception {
return Page.of(entities.values());
}
@Override
public void deleteEntity(Integer entityId) throws Exception {
entities.remove(entityId);
}
@Override
public Entity postEntity(Entity entity) throws Exception {
entities.put(entity.getId(), entity);
return entity;
}
@Override
public Entity putEntity(Integer entityId, Entity entity) throws Exception {
entities.put(entity.getId(), entity);
return entity;
}
\end{verbatim}
For the collection, you return a \texttt{Page} object based on a list
but there are also utility methods that return the pagination
information:
\begin{verbatim}
Page.of(list, pagination, totalCount)
\end{verbatim}
Don't touch the interfaces or the base classes (those are regenerated
every time you run REST Builder). Like
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}, you only have to maintain the implementation classes, and if
you change the API, run REST Builder again and the interfaces are
updated. Your business logic could call other REST APIs, use Service
Builder or another persistence mechanism.
\section{Development Cycle}\label{development-cycle}
While implementing your API's business logic, you'll typically improve
your API by adding parameters or other paths. For that, you'll modify
the OpenAPI profile and regenerate the API again calling the
\texttt{buildREST} command.
The cycle starts anew until you get to the final state and deploy your
APIs. They become available at this URL pattern:
\begin{verbatim}
http://localhost:8080/o/[application class name]/[OpenAPI version]/
\end{verbatim}
You can also execute \texttt{jaxrs:check} in the OSGi console to see all
the JAX-RS endpoints.
GraphQL paths and entities are added automatically to the default
GraphQL endpoint:
\begin{verbatim}
localhost:8080/o/graphql
\end{verbatim}
You can disable GraphQL generation by adding
\texttt{generateGraphQL:\ false} to your \texttt{rest-config.yaml}
(\texttt{generateREST} controls the generation of the REST endpoints).
\section{Wrapping Up}\label{wrapping-up}
So\ldots{} that's all!
When everything is ready, you might want to consider publishing your
Headless API to Swaggerhub so others can consume it. You can use the
following URL pattern for that:
\begin{verbatim}
http://localhost:8080/o/[application name]/[application version]/openapi.yaml
\end{verbatim}
The URL for the example above, therefore, would be
\begin{verbatim}
http://localhost:8080/o/headless-test/v1.0/openapi.yaml
\end{verbatim}
This URL has the content of \texttt{rest-openapi.yaml} plus the classes
that REST Builder generated for you.
\chapter{Managing Collections in REST
Builder}\label{managing-collections-in-rest-builder}
\section{Pagination}\label{pagination-3}
To add pagination to your endpoints, add \texttt{page} and
\texttt{pageSize} as query parameters to your OpenAPI profile, like
this:
\begin{verbatim}
- in: query
name: page
schema:
type: integer
- in: query
name: pageSize
schema:
type: integer
\end{verbatim}
Those two parameters add a \texttt{Pagination\ pagination} parameter in
the method signature to restrict the number of entries to return in the
\texttt{Page.of} constructor.
Pagination is highly recommended for entities that can have many
elements, to avoid very large requests.
\section{Filtering, sorting and
searching}\label{filtering-sorting-and-searching}
Adding support for filtering, sorting, and searching is trickier. The
first step is to add the query parameters to the OpenAPI profile, like
this:
\begin{verbatim}
- in: query
name: filter
schema:
type: string
- in: query
name: search
schema:
type: string
- in: query
name: sort
schema:
type: string
\end{verbatim}
The method signature then receives a Sort object, a Filter object, and
string with the search request. Those objects use
\href{/docs/7-2/frameworks/-/knowledge_base/f/model-entity-indexing-framework}{Liferay's
indexing framework}. This gives you many benefits, like support for
permissions out-of-the-box and having to write very little code to
achieve sort, filter, and search.
So first, you must make sure your entity is indexed and uses the
indexing framework.
Once that's done you have three things to do:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Add an \texttt{EntityModel} to translate between our indexing
framework and your code
\item
Inject your \texttt{entityModel} into your resource implementation.
\item
Call Search utilities to avoid boilerplate code.
\end{enumerate}
\section{Add an EntityModel}\label{add-an-entitymodel}
The \texttt{EntityModel} is a class that translates the name the
property has in your API to the name used to index it.
\begin{verbatim}
public class EntityEntityModel implements EntityModel {
public EntityEntityModel() {
_entityFieldsMap = EntityModel.toEntityFieldsMap(
new StringEntityField(
"name", locale -> Field.getSortableFieldName(Field.NAME))
);
}
@Override
public Map getEntityFieldsMap() {
return _entityFieldsMap;
}
private final Map _entityFieldsMap;
}
\end{verbatim}
The \texttt{EntityModel} decouples the way you filter/sort from the way
you index the information. You could use one field to sort, backed
internally by several indexed fields or vice-versa.
\section{Inject Your EntityModel}\label{inject-your-entitymodel}
Injecting your \texttt{EntityModel} is really easy, our resource
implementation just has to implement the \texttt{EntityModelResource}
interface.
This entity model is simple and doesn't have any dynamic fields, so you
can instantiate it directly and return it in the \texttt{getEntityModel}
method, like this:
\begin{verbatim}
@Component(
properties = "OSGI-INF/liferay/rest/v1_0/entity.properties",
scope = ServiceScope.PROTOTYPE, service = EntityResource.class
)
public class EntityResourceImpl extends BaseEntityResourceImpl implements
EntityModelResource {
...
@Override
public EntityModel getEntityModel(MultivaluedMap multivaluedMap) {
return _entityEntityModel;
}
private EntityEntityModel _entityEntityModel = new EntityEntityModel();
}
\end{verbatim}
\section{Call search utilities}\label{call-search-utilities}
Finally, you must call \texttt{SearchUtil.search()} that links
everything together. It requires these parameters:
\texttt{booleanQueryUnsafeConsumer}: a boolean query to restrict the
information we want to retrieve.
\texttt{filter}: pass-through of the filter object.
\texttt{indexerClass}: the class of the entity that to filter/search.
\texttt{keywords}: pass-through of the search string.
\texttt{pagination}: pass-through of the pagination object (to read the
row requested).
\texttt{queryConfigUnsafeConsumer}: the configuration for the fields
that you want to return, typically the id to do a query later, in the
\texttt{transformUnsafeFunction}.
\texttt{searchContextUnsafeConsumer}: global configuration of the query.
\texttt{transformUnsafeFunction}: the function that transforms from
\texttt{Document} (of the indexing framework) to your entity, either
searching in the database, your persistence, another API, etc.
\texttt{sorts}: pass-through of the sorts object.
The code would be similar to this:
\begin{verbatim}
@Override
public Page getEntitiesPage(
String search, Filter filter, Pagination pagination, Sort[] sorts)
throws Exception {
return SearchUtil.search(
booleanQuery -> {},
filter, Entity.class, search, pagination,
queryConfig -> queryConfig.setSelectedFieldNames(
Field.ENTRY_CLASS_PK),
searchContext -> searchContext.setCompanyId(contextCompany.getCompanyId()),
document -> new Entity(), //FILL with your implementation
sorts);
}
\end{verbatim}
\section{Using Your filter, search, and
sort}\label{using-your-filter-search-and-sort}
Lifeay uses OData to express our filter queries, following this
\href{/docs/7-2/frameworks/-/knowledge_base/f/filter-sort-and-search\#filter}{syntax}.
And that's it! Now you can can filter/search and sort by the fields you
defined in your \texttt{EntityModel}.
\chapter{REST Builder Scaffolding}\label{rest-builder-scaffolding}
Apart from the JAX-RS annotations, basic structure and GraphQL support,
there are some other things the REST Builder provides:
\begin{itemize}
\tightlist
\item
Context Fields
\item
Automatic Transactions
\item
Test Generation
\item
Client Generation
\item
Common Utilities
\end{itemize}
These are useful and time saving additions to your development cycle.
\section{Context fields}\label{context-fields}
The \texttt{ResourceImpl} classes are JAX-RS-compliant resources. You
can add \texttt{@Context} injections, mix JAX-RS endpoints, and use full
power of the JAX-RS standard.
REST Builder injects several fields (as protected fields in the base
class) to help you implement new APIs, like these:
\begin{itemize}
\tightlist
\item
\texttt{contextAcceptLanguage}, containing the current
\texttt{Locale}.
\item
\texttt{contextCompany}, containing the current \texttt{Company}.
\item
\texttt{contextHttpServletRequest}, containing the
\texttt{HttpServletRequest}.
\item
\texttt{contextHttpServletResponse}, containing the
\texttt{HttpServletResponse}.
\item
\texttt{contextUser}, containing the current logged-in \texttt{User}.
\item
\texttt{contextUriInfo}, containing the \texttt{UriInfo} information
(paths, endpoints).
\end{itemize}
\section{Automatic transactions}\label{automatic-transactions}
One little-known feature of REST Builder is that it wraps your API calls
in transactions if the HTTP verb used is POST, PUT, PATCH or DELETE
(doesn't apply for GET operations).
If you need to do several Service Builder calls in a sequence, you don't
have to wrap them in a transaction; it happens automatically. The
transaction commits if no exception is thrown and rolls back if an
exception bubbles up to the resource implementation method.
\section{Test generation}\label{test-generation}
You can generate integration tests if you specify a \texttt{testDir}
property:
\begin{verbatim}
testDir: "../headless-admin-taxonomy-test/src/testIntegration/java"
\end{verbatim}
REST Builder generates integration tests under the -test module. Those
tests check the API using a REST client. They check the response, doing
an end-to-end test involving all steps from parsing the request to
returning JSON.
The generated tests are ignored by default and, depending on the path,
may force you to implement a creation method (to be able to add content
to either update/delete or retrieve it).
The
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/headless/headless-delivery/headless-delivery-test}{headless-delivery-test}
project contains many examples.
\section{Client generation}\label{client-generation}
You can generate a Java client if you specify a \texttt{clientDir}
property:
\begin{verbatim}
clientDir: "../headless-admin-taxonomy-client/src/main/java"
\end{verbatim}
This is a Java, typed, client that interacts with the APIs using static
methods. The client project contains all the methods to call your paths
and parses the requests and responses.
\section{Common utilities}\label{common-utilities}
REST Builder provides several JAX-RS utilities, from exception mappers
for the most common exceptions in the portal, XML and JSON Body
Readers/Writers, Site validation (that works with \texttt{siteId} and
\texttt{siteKey}), and support for Bean Validation.
There are also utility libraries that can be useful when developing new
APIs:
\begin{itemize}
\tightlist
\item
\texttt{ContentLanguageUtil}, to deal with the
\texttt{ContentLanguage} header.
\item
\texttt{JaxRsLinkUtil}, to create links between APIs.
\item
\texttt{LocalDateTimeUtil}, to transform between date formats.
\item
\texttt{LocalizedMapUtil}, to create maps with locales as keys.
\item
\texttt{SearchUtil}, to use the search framework to return Pages of
content.
\item
\texttt{TransformUtil}, to deal with lambdas and conversions.
\end{itemize}
\chapter{Support for oneOf, anyOf and
allOf}\label{support-for-oneof-anyof-and-allof}
OpenAPI 3.0 added several ways of using inheritance and composition to
create complex schemas. Specifically, it added support for
\href{https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/}{\emph{allOf},
\emph{anyOf,} and \emph{oneOf}}, with these semantics:
\begin{itemize}
\tightlist
\item
allOf -- the value validates against all the subschemas
\item
anyOf -- the value validates against any of the subschemas
\item
oneOf -- the value validates against exactly one of the subschemas
\end{itemize}
Next, you'll learn each option's syntax and how its code is generated.
\section{allOf}\label{allof}
With \texttt{allOf} you can use the power of composition to create an
entity that combines several others. It's the most potent way of reusing
code without losing expressiveness and granularity: you can define small
entities that can be reused by composing several to create a larger
entity.
To use \texttt{allOf} you must follow this syntax:
\begin{verbatim}
EntityA:
properties:
nameA:
type: string
EntityB:
properties:
nameB:
type: string
EntityC:
allOf:
- $ref: '#/components/schemas/EntityA'
- $ref: '#/components/schemas/EntityB'
\end{verbatim}
This OpenAPI syntax generates the following Java code inside the
\texttt{EntityC} class:
\begin{verbatim}
@Schema
@Valid
public EntityA getEntityA() {
return entityA;
}
@Schema
@Valid
public EntityB getEntityB() {
return entityB;
}
\end{verbatim}
\section{oneOf}\label{oneof}
\texttt{OneOf} is the simplest of the generics properties. It defines a
property that can have different types. Since Java doesn't support Union
Types, use an Object to model the property:
\begin{verbatim}
EntityA:
properties:
nameA:
type: string
EntityB:
properties:
nameB:
anyOf:
- $ref: "#/components/schemas/EntityA"
- type: object
properties:
name:
type: string
\end{verbatim}
This syntax generates the following Java code:
\begin{verbatim}
@Schema
@Valid
public Object getNameB() {
return nameB;
}
\end{verbatim}
\section{anyOf}\label{anyof}
The final generic keyword, \texttt{anyOf} leverages
\texttt{JsonSubTypes} to extend entities with properties using
inheritance. You can define parent relationships (in this example,
\texttt{EntityC}) with two children containing the properties of the
parent and their own properties. Here's how to define YAML to use
inheritance:
\begin{verbatim}
EntityC:
oneOf:
- properties:
nameA:
type: string
- properties:
nameB:
type: string
properties:
nameC:
type: string
\end{verbatim}
This generates a parent class and two children:
\begin{verbatim}
@JsonSubTypes(
{
@JsonSubTypes.Type(name = "nameA", value = NameA.class),
@JsonSubTypes.Type(name = "nameB", value = NameB.class)
}
)
@JsonTypeInfo(
include = JsonTypeInfo.As.PROPERTY, property = "childType",
use = JsonTypeInfo.Id.NAME
)
@Generated("")
@GraphQLName("EntityC")
@JsonFilter("Liferay.Vulcan")
@XmlRootElement(name = "EntityC")
public class EntityC {
@Schema
public String getNameC() {
return nameC;
}
public void setNameC(String nameC) {
this.nameC = nameC;
}
\end{verbatim}
And two children classes look like this:
\begin{verbatim}
@JsonTypeInfo(
defaultImpl = NameA.class, include = JsonTypeInfo.As.PROPERTY,
property = "childType", use = JsonTypeInfo.Id.NAME
)
@Generated("")
@GraphQLName("NameA")
@JsonFilter("Liferay.Vulcan")
@XmlRootElement(name = "NameA")
public class NameA extends EntityC {
@Schema
public String getNameA() {
return nameA;
}
public void setNameA(String nameA) {
this.nameA = nameA;
}
\end{verbatim}
\chapter{REST Builder Liferay
Conventions}\label{rest-builder-liferay-conventions}
Liferay's headless APIs follow several patterns and conventions to
provide consistency and uniformity in the APIs.
Below is a list the most important ones. It's a living list.
Improvements are being made all the time, so check back to stay up to
date on the changes.
\section{YAML \& OpenAPI restrictions}\label{yaml-openapi-restrictions}
\begin{itemize}
\tightlist
\item
\emph{Tags are required}. We can't assign a class to a DELETE
operation (doesn't return anything, doesn't receive an entity) so we
need the tag to assign the method to a Java class.
\item
Responses must return a status code (default is not supported).
\item
Paths must be quoted.
\item
Paths only contain the method path (application and version are
inherited from the JAX-RS application).
\end{itemize}
\section{Conventions}\label{conventions}
\begin{itemize}
\item
We use path parameters for information that is required (like the id
in a DELETE operation) and query parameters for optional information
(filtering, sorting\ldots)
\item
We don't expose \texttt{className}. If you need to return information
like the \texttt{className}, use \texttt{contentType} keyword.
\end{itemize}
\chapter{The Workflow Framework}\label{the-workflow-framework}
Blogs Entries, Journal Articles, and Forms Entries are just a few Assets
supporting workflow. There's nothing stopping you from likewise enabling
workflow for your custom Assets. Discover here how the workflow
framework works, and find the steps and code samples for enabling your
custom entities to use the workflow capabilities in subsequent articles.
A workflow process is a set of steps that an Asset must proceed through
before it's marked with the workflow status \emph{Approved}. The steps
are defined in an XML file called a
\href{/7-2/reference/-/knowledge_base/r/crafting-xml-workflow-definitions}{workflow
definition}. Each Asset is configured to run through a specific workflow
definition via the \href{/7-2/user/-/knowledge_base/u/workflow}{Control
Panel}.
\begin{figure}
\centering
\includegraphics{./images/workflow-configuration.png}
\caption{Enable workflow on your custom Asset, and it can be sent
through a workflow process just like a native Asset.}
\end{figure}
The workflow status is a database field that must be present for an
entity to support workflow. If a database has the status field, but no
workflow code has been written, it's auto-marked \emph{Approved} by
Liferay's Service Builder infrastructure, to assure that everything
works smoothly by default.
\section{Supporting Workflow in the
Database}\label{supporting-workflow-in-the-database}
There are several database fields that must be present for an Asset to
support workflow:
\texttt{int\ status} represents the workflow status of each Asset.
\texttt{long\ statusByUserId} is the ID of the user that set the status
(for example, the initial User that hit the Submit for Publication
button to add a new Asset.
\texttt{String\ statusByUserName} is the User Name of the User that set
the status of the Asset.
\texttt{Date\ statusDate} is the date/time when the status was set.
For Service Builder applications, add these as entity columns in the
\texttt{service.xml} file, run Service Builder, and you're good to go.
\section{Setting the Status Fields}\label{setting-the-status-fields}
Once the database table has the proper status fields, set them in your
Entity's \texttt{addEntity} service method. Initially, set the status as
a DRAFT. It's what the workflow framework expects of an entity as it
enters the workflow process. The status is an \texttt{int}, but you
don't have to remember which number corresponds to the DRAFT status.
Instead, use the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-kernel/src/com/liferay/portal/kernel/workflow/WorkflowConstants.java}{\texttt{WorkflowConstants}}
in \texttt{portal-kernel}. For a draft, pass in
\begin{verbatim}
WorkflowConstants.STATUS_DRAFT
\end{verbatim}
If you're curious, the \texttt{int} represented by this constant is
\texttt{2}. Another important status, APPROVED, is represented by the
\texttt{int} value \texttt{0} and the constant
\begin{verbatim}
WorkflowConstants.STATUS_APPROVED
\end{verbatim}
The User fields (\texttt{statusByUserID} and \texttt{statusByUserName})
are easy, since the \texttt{userId} of the User making the
\texttt{addEntity} request is part of the request itself, and passed
into the \texttt{addEntity} method for most assets. Use the ID directly
as the \texttt{statusByUserId}, and get the full name associated with
the User by using the ID to retrieve the \texttt{User} object.
\begin{verbatim}
entity.setStatusByUserId(userId);
entity.setStatusByUserName(user.getFullName());
\end{verbatim}
The \texttt{statusDate} is usually best set as the date the entity was
modified, and is part of the Service Context in the request:
\begin{verbatim}
entity.setStatusDate(serviceContext.getModifiedDate(null));
\end{verbatim}
Once the status dates are set, the entity is ready to be sent into the
workflow framework.
\section{Sending the Entity to the Workflow
Framework}\label{sending-the-entity-to-the-workflow-framework}
When an entity is added to the database, the application must detect
whether workflow is enabled. If not, it automatically marks the entity
as approved so it appears in the UI. Otherwise, it's left in draft
status and the workflow back-end handles it. Thankfully, this whole
process is easily done with a call to
\texttt{WorkflowHandlerRegistryUtil.startWorkflowInstance} in your
persistence code.
\section{Allowing the Workflow Framework to Handle the
Entity}\label{allowing-the-workflow-framework-to-handle-the-entity}
Once the entity is sent to the Workflow Framework, much of the process
is automated, and you need not worry about the details. Write one class
that gives the framework some information on how to process the entity.
It's called a workflow handler
(\texttt{WorkflowHandler\textless{}T\textgreater{}}), and you can create
it by extending the handy abstract implementation,
\texttt{BaseWorkflowHandler\textless{}T\textgreater{}}.
The workflow handler usually goes in the module containing service
implementations. It's nice to keep your back-end code separate from your
view layer and controller (ala the MVC pattern).
Make your workflow handler a Component class so it can be registered
properly with OSGi runtime. It requires one Component property,
\texttt{model.class.name}, which is the fully qualified class name for
class you pass as the type parameter in the class declaration.
In addition to the property, declare the type of service you're
providing in the Component: \texttt{WorkflowHandler.class}.
Workflow handlers extending the \texttt{BaseWorkflowHandler} must
override three methods:
\texttt{getClassName} returns the model class's fully qualified class
name (\texttt{com.my.app.package.model.FooEntity}, for example).
\texttt{getType} returns the model resource name
(\texttt{model.resource.com.my.app.package.model.FooEntity}, for
example).
\texttt{updateStatus} does most of the heavy lifting here. It returns a
call to a local service method of the same name (for example,
\texttt{FooEntityLocalService.updateStatus}), so the status returned
from the workflow back-end can be persisted to the entity table in the
database. The \texttt{updateStatus} method needs a user ID, the primary
key for the class (for example, \texttt{fooEntityId}), the workflow
status, the service context, and the workflow context. The status and
the workflow context can be obtained from the workflow back-end. The
other parameters can be obtained from the workflow context.
\section{Supporting Workflow in the Service
Layer}\label{supporting-workflow-in-the-service-layer}
The service layer must update the status of the entity when it returns
from the Workflow Framework. Make an \texttt{updateStatus} method for
this purpose, and make sure, at a minimum, to set the status fields
again as the Asset comes out of the Workflow Framework, and call the
persistence layer's \texttt{update} method.
After that, provide any additional logic you might want, like checking
the status and updating the Asset's visibility (using the
\texttt{assetEntryLocalService}) based on the condition (visible if
\emph{Approved}, not visible is any other status).
Return the entry once you're through here.
\section{Database Cleanup: Delete the Workflow Instance
Links}\label{database-cleanup-delete-the-workflow-instance-links}
When you send an entity to the workflow framework via the
\texttt{startWorkflowInstance} call, it creates an entry in the
\texttt{workflowinstancelink} database table. In your service layer's
deletion logic, you must delete the workflow instance links. This
\texttt{delete} call ensures there are no orphaned entries in the
\texttt{workflowinstancelinks} table.
To get the \texttt{WorkflowInstanceLocalService} injected into your
\texttt{*LocalServiceBaseImpl} so you can call its methods in the
\texttt{LocalServiceImpl}, add a \texttt{reference\ entity} to your
entity declaration in \texttt{service.xml}, specifying
\texttt{WorkflowInstancelink}.
\section{Updating the User Interface}\label{updating-the-user-interface}
After you finish all the backend work, update your UI. Some common tasks
here include:
\begin{itemize}
\item
In any public facing portion of the application (accessible to guest
Users), don't display the entity if the status is anything except
\emph{Approved}. This task requires the creation of an additional
\texttt{finder} method that accounts for workflow status, and a
corresponding \texttt{getter} to expose it in the service layer.
\item
In administrative portions of the application, display the entities,
but also display their workflow status. There's a tag library for
this.
\end{itemize}
See the next article for more concrete steps and code snippets.
\chapter{Liferay's Workflow
Framework}\label{liferays-workflow-framework}
To workflow-enable your entities,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Create a Workflow Handler
\item
Update the Service Layer
\item
Update the User Interface
\end{enumerate}
Time to get started.
\section{Creating a Workflow Handler}\label{creating-a-workflow-handler}
If you're in a Service Builder application, the workflow handler goes in
your \texttt{-service} module.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a Component class that extends
\texttt{BaseWorkflowHandler\textless{}T\textgreater{}}.
\begin{verbatim}
@Component(immediate = true, service = WorkflowHandler.class)
public class FooEntityWorkflowHandler extends BaseWorkflowHandler
\end{verbatim}
\item
Override three methods in the workflow handler.
\begin{verbatim}
@Override
public String getClassName() {
return FooEntity.class.getName();
}
@Override
public String getType(Locale locale) {
return ResourceActionsUtil.getModelResource(locale, getClassName());
}
@Override
public FooEntity updateStatus(int status, Map workflowContext) throws PortalException {
... }
\end{verbatim}
Most of the heavy lifting is in the \texttt{updateStatus} method. It
returns a call to a local service method of the same name (for
example, \texttt{FooEntityLocalService.updateStatus}), so the status
returned from the workflow back-end can be persisted to the entity
table in the database.
The \texttt{updateStatus} method needs a user ID, the primary key for
the class (for example, \texttt{fooEntityId}), the workflow status,
the service context, and the workflow context. The status and the
workflow context can be obtained from the workflow back-end. The other
parameters can be obtained from the workflow context. Here's an
example \texttt{updateStatus} method:
\begin{verbatim}
@Override
public FooEntity updateStatus(
int status, Map workflowContext)
throws PortalException {
long userId = GetterUtil.getLong(
(String)workflowContext.get(WorkflowConstants.CONTEXT_USER_ID));
long classPK = GetterUtil.getLong(
(String)workflowContext.get(
WorkflowConstants.CONTEXT_ENTRY_CLASS_PK));
ServiceContext serviceContext = (ServiceContext)workflowContext.get(
WorkflowConstants.CONTEXT_SERVICE_CONTEXT);
return _fooEntityLocalService.updateStatus(
userId, classPK, status, serviceContext);
}
\end{verbatim}
\end{enumerate}
Now your entity can be handled by Liferay's workflow framework. Next,
update the service methods to account for workflow status and add a new
method to update the status of an entity in the database.
\section{Updating the Service Layer}\label{updating-the-service-layer}
In most Liferay applications,
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} is used to create database fields. First, you must update the
service layer:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make sure your entity database table has \texttt{status},
\texttt{statusByUserId}, \texttt{statusByUserName}, and
\texttt{statusDate} fields.
\begin{verbatim}
\end{verbatim}
\item
Wherever you're setting the other database fields in your persistence
code, set the workflow status as a draft and set the other fields.
\begin{verbatim}
fooEntity.setStatus(WorkflowConstants.STATUS_DRAFT);
fooEntity.setStatusByUserId(userId);
fooEntity.setStatusByUserName(user.getFullName());
fooEntity.setStatusDate(serviceContext.getModifiedDate(null));
\end{verbatim}
With Service Builder driven Liferay applications, this is in the local
service implementation class (\texttt{-LocalServiceImpl}).
\item
At the end of any method that adds a new entity to your database, call
the workflow service to enable sending the entity into the workflow
backend:
\begin{verbatim}
WorkflowHandlerRegistryUtil.startWorkflowInstance(
fooEntity.getCompanyId(), fooEntity.getGroupId(), fooEntity.getUserId(),
FooEntity.class.getName(), fooEntity.getPrimaryKey(), fooEntity,
serviceContext);
\end{verbatim}
\item
Implement the \texttt{updateStatus} method that must be called in the
workflow handler. In the end, persist the updated entity to the
database.
\begin{verbatim}
fooEntity.setStatus(status);
fooEntity.setStatusByUserId(user.getUserId());
fooEntity.setStatusByUserName(user.getFullName());
fooEntity.setStatusDate(serviceContext.getModifiedDate(now));
fooEntityPersistence.update(fooEntity);
\end{verbatim}
\item
Do anything else that might make sense here, like changing the
visibility of the asset depending on its workflow status:
\begin{verbatim}
if (status == WorkflowConstants.STATUS_APPROVED) {
assetEntryLocalService.updateVisible(
FooEntity.class.getName(), fooEntityId, true);
}
else {
assetEntryLocalService.updateVisible(
FooEntity.class.getName(), fooEntityId, false);
}
\end{verbatim}
Here's what a full \texttt{updateStatus} method might look like:
\begin{verbatim}
@Indexable(type = IndexableType.REINDEX)
public FooEntity updateStatus(
long userId, long fooEntityId, int status, ServiceContext serviceContext
) throws PortalException, SystemException {
User user = userLocalService.getUser(userId);
FooEntity fooEntity = getFooEntity(fooEntityId);
fooEntity.setStatus(status);
fooEntity.setStatusByUserId(userId);
fooEntity.setStatusByUserName(user.getFullName());
fooEntity.setStatusDate(new Date());
fooEntityPersistence.update(fooEntity);
if (status == WorkflowConstants.STATUS_APPROVED) {
assetEntryLocalService.updateVisible(
FooEntity.class.getName(), fooEntityId, true);
}
else {
assetEntryLocalService.updateVisible(
FooEntity.class.getName(), fooEntityId, false);
}
return fooEntity;
}
\end{verbatim}
\item
Add a call to \texttt{deleteWorkflowInstanceLinks} in the
\texttt{deleteEntity} method:
\begin{verbatim}
workflowInstanceLinkLocalService.deleteWorkflowInstanceLinks(
fooEntity.getCompanyId(), fooEntity.getGroupId(),
FooEntity.class.getName(), fooEntity.getFooEntityId());
\end{verbatim}
To get the \texttt{WorkflowInstanceLocalService} injected into your
\texttt{*LocalServiceBaseImpl} so you can call its methods in the
\texttt{LocalServiceImpl}, add this to your entity declaration in
\texttt{service.xml}:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
For an example of a fully implemented \texttt{updateStatus} method, see
the
\texttt{com.liferay.portlet.blogs.service.impl.BlogsEntryLocalServiceImpl}
class in \texttt{portal-impl}.
Once you've accounted for workflow status in your database and service
layer, there's only one thing left to do: update the user interface.
\section{Workflow Status and the View
Layer}\label{workflow-status-and-the-view-layer}
This is dependent on the needs of your application, but often involves
the following these steps:
\textbf{Display only approved entities:}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
If you're using Service Builder, define your finder in your
application's \texttt{service.xml} and let Service Builder generate it
for you.
\begin{verbatim}
\end{verbatim}
\item
Make sure you have a getter in your service layer that uses the new
finder.
\begin{verbatim}
public List getFooEntities(long groupId, int status)
throws SystemException {
return fooEntityPersistence.findByG_S(
groupId, WorkflowConstants.STATUS_APPROVED);
}
\end{verbatim}
\item
Finally, update your JSP to use the appropriate getter.
\begin{verbatim}
...
\end{verbatim}
\end{enumerate}
\textbf{Display the workflow status:}
When you want to display the workflow status, use the
\texttt{\textless{}aui:worklfow-status\textgreater{}} tag.
\begin{verbatim}
\end{verbatim}
Once your user interface accounts for workflow, your Liferay application
is fully workflow enabled.
\chapter{WYSIWYG Editors}\label{wysiwyg-editors}
WYSIWYG editors are an important part of content creation. Liferay's
platform supports several different editors, including CKEditor,
TinyMCE, and our flagship, AlloyEditor. This section shows how to
customize these WYSIWYG editors for your apps and sites.
\chapter{Adding a WYSIWYG Editor to a
Portlet}\label{adding-a-wysiwyg-editor-to-a-portlet}
It's easy to include WYSIWYG editors in your portlet, thanks to the
\texttt{\textless{}liferay-editor:editor\ /\textgreater{}} tag. Follow
these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the liferay-editor taglib declaration to your portlet's JSP:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/editor" prefix="liferay-editor" %>
\end{verbatim}
\item
Add the editor to your JSP with the
\texttt{\textless{}liferay-editor:editor\ /\textgreater{}} tag.
Configure it using the attributes shown in the table below:
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
Attribute | Type | Description |
--- | --- | --- |
`autoCreate` | `java.lang.String` | Whether to show the HTML edit view of the editor initially |
`contents` | `java.lang.String` | Sets the initial contents of the editor |
`contentsLanguageId` | `java.lang.String` | Sets the language ID for the input editor's text |
`cssClass` | `java.lang.String` | A CSS class for styling the component. |
`data` | `java.util.Map` | Data that can be used as the editorConfig |
`editorName` | `java.lang.String` | The editor you want to use (alloyeditor, ckeditor, tinymce, simple) |
`name` | `java.lang.String` | A name for the input editor. The default value is `editor`. |
`onBlurMethod` | `java.lang.String` | A function to be called when the input editor loses focus. |
`onChangeMethod` | `java.lang.String` | A function to be called on a change in the input editor. |
`onFocusMethod` | `java.lang.String` | A function to be called when the input editor gets focus. |
`onInitMethod` | `java.lang.String` | A function to be called when the input editor initializes. |
`placeholder` | `java.lang.String` | Placeholder text to display in the input editor. |
`showSource` | `java.lang.String` | Whether to enable editing the HTML source code of the content. The default value is `true`. |
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
See the [taglibdocs](https://docs.liferay.com/dxp/apps/frontend-editor/latest/taglibdocs/liferay-editor/editor.html)
for the complete list of supported attributes.
Below is an example configuration:
```html
```
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Optionally pass JavaScript functions through the
\texttt{onBlurMethod}, \texttt{onChangeMethod},
\texttt{onFocusMethod}, and \texttt{onInitMethod} attributes. Here is
an example configuration that uses the \texttt{onInitMethod} attribute
to pass a JavaScript function called \texttt{OnDescriptionEditorInit}:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/editor" prefix="liferay-editor" %>
\end{verbatim}
\begin{verbatim}
function OnDescriptionEditorInit() {
document.getElementById(
' myAlloyEditor'
).setAttribute('contenteditable', false);
}
\end{verbatim}
\end{enumerate}
As you can see, it's easy to include WYSIWYG editors in your portlets!
\section{Related Topics}\label{related-topics-137}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-new-behavior-to-an-editor}{Adding
New Behavior to an Editor}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/modifying-an-editors-configuration}{Modifying
an Editor's Configuration}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/alloyeditor}{Modifying
the AlloyEditor}
\end{itemize}
\chapter{Modifying an Editor's
Configuration}\label{modifying-an-editors-configuration}
You can use many different kinds of WYSIWYG editors to edit content in
portlets. Depending on the content you're editing, you may want to
modify the editor to provide a customized configuration for your needs.
In this article, you'll learn how to modify the default configuration
for Liferay DXP's supported WYSIWYG editors to meet your requirements.
To modify the editor's configuration, create a module with a component
that implements the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/editor/configuration/EditorConfigContributor.html}{\texttt{EditorConfigContributor}}
interface. Follow these steps to modify one of Liferay DXP's WYSIWYG
editors:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
an OSGi module}.
\item
Open the portlet's \texttt{build.gradle} file and update the
\texttt{com.liferay.portal.kernel} \texttt{version} to
\texttt{4.13.1}. This is the version bundled with the Liferay DXP
release.
\item
Create a unique package name in the module's \texttt{src} directory,
and create a new Java class in that package that extends the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/editor/configuration/BaseEditorConfigContributor.html}{\texttt{BaseEditorConfigContributor}}
class:
\item
Create a component class that implements the
\texttt{EditorConfigContributor} service:
\begin{verbatim}
@Component(
property = {
},
service = EditorConfigContributor.class
)
\end{verbatim}
\item
Add the following imports:
\begin{verbatim}
import com.liferay.portal.kernel.editor.configuration.BaseEditorConfigContributor;
import com.liferay.portal.kernel.editor.configuration.EditorConfigContributor;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory;
import com.liferay.portal.kernel.theme.ThemeDisplay;
\end{verbatim}
\item
Specify the editor's name, editor's configuration key, and/or the
portlet name(s) where the editor resides. These three properties can
be specified independently, or together, in any order. See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/editor/configuration/EditorConfigContributor.html}{\texttt{EditorConfigContributor}}
interface's Javadoc for more information about the available
properties and how to use them. The example configuration below
modifies the AlloyEditor's Content Editor, identified by the
\texttt{contentEditor} configuration key and \texttt{alloyeditor} name
key.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** If you're targeting all editors for a portlet, the
`editor.config.key` is not required. For example, if you just want to target
the Web Content portlet's editors, you can provide the configuration below:
```java
@Component(
property = {"editor.name=ckeditor",
"javax.portlet.name=com_liferay_journal_web_portlet_JournalPortlet",
"service.ranking:Integer=100"
}
```
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
Two portlet names are declared (Blogs and Blogs Admin), specifying that the
service applies to the content editors in those portlets. Lastly, the
configuration overrides the default one by providing a higher
[service ranking](/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service):
```java
@Component(
property = {
"editor.config.key=contentEditor", "editor.name=alloyeditor",
"javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet",
"javax.portlet.name=com_liferay_blogs_web_portlet_BlogsAdminPortlet",
"service.ranking:Integer=100"
},
service = EditorConfigContributor.class
)
```
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
**NOTE:** If you want to create a global configuration that applies to an
editor everywhere it's used, you must create two separate configurations:
one configuration that targets just the editor and a second configuration
that targets the Blogs and Blogs Admin portlets. For example, the two
separate configurations below apply the updates to AlloyEditor everywhere
it's used:
Configuration one:
```java
@Component(
immediate = true,
property = {
"editor.name=alloyeditor",
"service.ranking:Integer=100"
},
service = EditorConfigContributor.class
)
```
Configuration two:
```java
@Component(
immediate = true,
property = {
"editor.name=alloyeditor",
"javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet",
"javax.portlet.name=com_liferay_blogs_web_portlet_BlogsAdminPortlet",
"service.ranking:Integer=100"
},
service = EditorConfigContributor.class
)
```
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{6}
\item
Override the \texttt{populateConfigJSONObject()} method to provide the
custom configuration for the editor. This method updates the original
configuration JSON object. It can also Update or delete existing
configurations, or any other configuration introduced by another
\texttt{*EditorConfigContributor}.
\begin{verbatim}
@Override
public void populateConfigJSONObject(
JSONObject jsonObject, Map inputEditorTaglibAttributes,
ThemeDisplay themeDisplay,
RequestBackedPortletURLFactory requestBackedPortletURLFactory) {
}
\end{verbatim}
\item
In the \texttt{populateConfigJSONObject} method, you must instantiate
a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/json/JSONObject.html}{\texttt{JSONObject}}
to hold the current configuration of the editor. For instance, you
could use the code snippet below to retrieve the available toolbars
for the editor:
\begin{verbatim}
JSONObject toolbars = jsonObject.getJSONObject("toolbars");
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** This toolbar configuration is only applicable for the AlloyEditor.
If you choose a configuration that is supported by multiple editors, you
could apply it to them all. To do this, you could specify all the editors
(e.g., `"editor.name=alloyeditor"`, `"editor.name=ckeditor"`,
`ckeditor_bbcode` etc.) in the `@Component` annotation of your
`EditorConfigContributor` implementation, as you did in step six. Use the
links the bottom of this article to view each editor's configuration
options and requirements.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{8}
\item
Now that you've retrieved the toolbar, you can modify it. The example
below adds a camera button to the AlloyEditor's Add toolbar. It
extracts the \emph{Add} buttons out of the toolbar configuration
object as a
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/json/JSONArray.html}{\texttt{JSONArray}},
and then adds the button to that \texttt{JSONArray}:
\begin{verbatim}
if (toolbars != null) {
JSONObject toolbarAdd = toolbars.getJSONObject("add");
if (toolbarAdd != null) {
JSONArray addButtons = toolbarAdd.getJSONArray("buttons");
addButtons.put("camera");
}
}
\end{verbatim}
The configuration JSON object is passed to the editor with the
modifications you've implemented in the
\texttt{populateConfigJSONObject} method.
\item
Finally, generate the module's JAR file and copy it to your
\texttt{deploy} folder. Once the module is installed and activated in
the service registry, your new editor configuration is available for
use.
\end{enumerate}
Liferay DXP supports several different types of WYSIWYG editors, which
include (among others):
\begin{itemize}
\tightlist
\item
\href{https://alloyeditor.com/api/1.5.0/Core.html}{AlloyEditor}
\item
\href{http://docs.ckeditor.com/\#!/api/CKEDITOR.config}{CKEditor}
\item
\href{http://www.tinymce.com/wiki.php/Configuration}{TinyMCE}
\end{itemize}
Make sure to visit each editor's configuration API to learn what each
editor offers for configuration settings.
\section{Related Topics}\label{related-topics-138}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-new-behavior-to-an-editor}{Adding
New Behavior to an Editor}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/alloyeditor}{Modifying
the AlloyEditor}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-a-wysiwyg-editor-to-a-portlet}{Adding
a WYSIWYG Editor to a Portlet}
\end{itemize}
\chapter{AlloyEditor}\label{alloyeditor}
AlloyEditor is a modern WYSIWYG editor built on top of CKEDITOR,
designed to create modern and gorgeous web content. AlloyEditor is the
default WYSIWYG editor.
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-website.png}
\caption{AlloyEditor is the default WYSIWYG editor built on top of
CKEditor.}
\end{figure}
This section shows how to modify the default AlloyEditor configuration
to meet your requirements.
\chapter{Adding Buttons to AlloyEditor's
Toolbars}\label{adding-buttons-to-alloyeditors-toolbars}
AlloyEditor's toolbars contain several useful functions out-of-the-box.
You may, however, want to customize the default configuration to include
a button you've created, to add an existing button to a toolbar, or to
add an
\href{/docs/7-2/reference/-/knowledge_base/r/ckeditor-plugin-reference-guide}{existing
CKEditor button that's bundled with Liferay DXP's AlloyEditor}. The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/editor/configuration/EditorConfigContributor.html}{\texttt{EditorConfigContributor}
interface}, provides everything you need to modify an editor's
configuration, including adding buttons to AlloyEditor's toolbars.
\href{https://docs.ckeditor.com/ckeditor4/latest/api/CKEDITOR_config.html}{CKEditor
Configuration settings} that modify the editor's behavior (excluding UI
modifications) can also be passed down through this configuration
object.
The \texttt{com.liferay.docs.my.button} module is used as an example
throughout this section. If you want to use it as a starting point for
your own configuration or follow along with the articles, you can
download the module's zip file from the
\href{https://github.com/liferay/liferay-docs/tree/7.1.x/develop/tutorials/code/osgi/modules/com.liferay.docs.my.button}{Github
repo}.
\chapter{Creating the OSGi Module and Configuring the
EditorConfigContributor
Class}\label{creating-the-osgi-module-and-configuring-the-editorconfigcontributor-class}
To add a button to the AlloyEditor's toolbars, you must first create an
OSGi component class of service type
\texttt{EditorConfigContributor.class}. Follow these steps to create and
configure the OSGi module:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
an OSGi module}, using
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-mvc-portlet-template}{Blade's
portlet template}:
\begin{verbatim}
blade create -t portlet -p com.liferay.docs.my.button -c
MyEditorConfigContributor my-new-button
\end{verbatim}
\item
Open the portlet's \texttt{build.gradle} file and update the
\texttt{com.liferay.portal.kernel} \texttt{version} to
\texttt{4.13.1}. This is the version bundled with the Liferay DXP
release.
\item
Open the portlet class you created in step one
(\texttt{MyEditorConfigContributor}) and add the following imports:
\begin{verbatim}
import com.liferay.portal.kernel.editor.configuration.BaseEditorConfigContributor;
import com.liferay.portal.kernel.editor.configuration.EditorConfigContributor;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory;
import com.liferay.portal.kernel.theme.ThemeDisplay;
\end{verbatim}
\item
Replace the \texttt{@Component} and properties with the properties
below:
\begin{verbatim}
@Component(
immediate = true,
property = {
"editor.name=alloyeditor",
"service.ranking:Integer=100"
},
service = EditorConfigContributor.class
)
\end{verbatim}
This targets AlloyEditor for the configuration and overrides the
default service by providing a higher
\href{/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service}{service
ranking}. If you want to target a more specific configuration, you can
find the available properties in the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/editor/configuration/EditorConfigContributor.html}{\texttt{EditorConfigContributor}
interface's Javadoc}.
\item
Extend \texttt{BaseEditorConfigContributor} instead of
\texttt{GenericPortlet}.
\item
Replace the \texttt{doView()} method and contents with the
\texttt{populateConfigJSONObject()} method shown below:
\begin{verbatim}
@Override
public void populateConfigJSONObject(
JSONObject jsonObject, Map inputEditorTaglibAttributes,
ThemeDisplay themeDisplay,
RequestBackedPortletURLFactory requestBackedPortletURLFactory) {
}
\end{verbatim}
\item
Inside the \texttt{populateConfigJSONObject()} method, retrieve the
AlloyEditor's toolbars:
\begin{verbatim}
JSONObject toolbarsJSONObject = jsonObject.getJSONObject("toolbars");
if (toolbarsJSONObject == null) {
toolbarsJSONObject = JSONFactoryUtil.createJSONObject();
}
\end{verbatim}
\item
If you're adding a button for one of the
\href{/docs/7-2/reference/-/knowledge_base/r/ckeditor-plugin-reference-guide}{CKEditor
plugins bundled with the AlloyEditor}, add the code below to retrieve
the extra plugins and add the plugin to the AlloyEditor's
configuration. The example below adds the \texttt{clipboard} CKEditor
plugin:
\begin{verbatim}
String extraPlugins = jsonObject.getString("extraPlugins");
if (Validator.isNotNull(extraPlugins)) {
extraPlugins = extraPlugins + ",ae_uibridge,ae_autolink,
ae_buttonbridge,ae_menubridge,ae_panelmenubuttonbridge,ae_placeholder,
ae_richcombobridge,clipboard";
}
else {
extraPlugins = "ae_uibridge,ae_autolink,ae_buttonbridge,ae_menubridge,
ae_panelmenubuttonbridge,ae_placeholder,ae_richcombobridge,clipboard";
}
jsonObject.put("extraPlugins", extraPlugins);
\end{verbatim}
AlloyEditor also comes with several plugins to bridge the gap between
the CKEditor's UI and the AlloyEditor's UI. These are prefixed with
the \texttt{ae\_} you see above. We recommend that you include them
all to ensure compatibility.
\end{enumerate}
The \texttt{*EditorConfigContributor} class is prepared. Now you must
choose which toolbar you want to add the button(s) to: the
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-a-button-to-the-add-toolbar}{Add
Toolbar} or one of the
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-a-button-to-a-styles-toolbar}{Styles
Toolbars}.
\section{Related Topics}\label{related-topics-139}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-new-behavior-to-an-editor}{Adding
New Behavior to an Editor}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/ckeditor-plugin-reference-guide}{CKEditor
Plugin Reference Guide}
\end{itemize}
\chapter{Adding a Button to the Add
Toolbar}\label{adding-a-button-to-the-add-toolbar}
The Add Toolbar appears in the AlloyEditor when your cursor is in the
editor and you click the Add button:
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-add-toolbar.png}
\caption{The Add toolbar lets you add content to the editor.}
\end{figure}
Follow these steps to add a button to the AlloyEditor's Add Toolbar:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the \texttt{populateConfigJSONObject()} method, retrieve the
Add Toolbar:
\begin{verbatim}
JSONObject addToolbar = toolbarsJSONObject.getJSONObject("add");
\end{verbatim}
\item
Retrieve the existing Add Toolbar buttons:
\begin{verbatim}
JSONArray addToolbarButtons = addToolbar.getJSONArray("buttons");
\end{verbatim}
\item
Add the button to the existing buttons. Note that the button's name is
case sensitive. The example below adds the \texttt{camera} button to
the Add Toolbar:
\begin{verbatim}
addToolbarButtons.put("camera");
\end{verbatim}
The camera button is just one of the buttons available by default with
AlloyEditor, but they are not all enabled. Here's the full list of
available buttons you can add to the Add Toolbar:
\begin{itemize}
\tightlist
\item
camera
\item
embed
\item
hline
\item
image
\item
table
\end{itemize}
See \href{https://alloyeditor.com/docs/features/camera.html}{here} for
an explanation of each button's features.
\item
Update the AlloyEditor's configuration with the changes you made:
\begin{verbatim}
addToolbar.put("buttons", addToolbarButtons);
toolbarsJSONObject.put("add", addToolbar);
jsonObject.put("toolbars", toolbarsJSONObject);
\end{verbatim}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy
your module} and create new content that uses the AlloyEditor---like a
blog entry or web content article---to see your new configuration in
action!
\end{enumerate}
The \texttt{com.liferay.docs.my.button} module's updated Add Toolbar is
shown in the figure below:
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-updated-add-toolbar.png}
\caption{The Updated Add toolbar lets you add pictures from a camera
directly to the editor.}
\end{figure}
\section{Related Topics}\label{related-topics-140}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-new-behavior-to-an-editor}{Adding
New Behavior to an Editor}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-a-button-to-a-styles-toolbar}{Adding
a Button to a Styles Toolbar}
\end{itemize}
\chapter{Adding a Button to a Styles
Toolbar}\label{adding-a-button-to-a-styles-toolbar}
A Styles Toolbar appears when content is selected or highlighted in
AlloyEditor. There are five Styles toolbars to choose from:
\texttt{embedurl}: Appears when embedded content is selected.
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-embedurl-toolbar.png}
\caption{The embed URL Styles toolbar lets you format embedded content
in the editor.}
\end{figure}
\texttt{image}: Appears when an image is selected.
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-image-toolbar.png}
\caption{The image Styles toolbar lets you format images in the editor.}
\end{figure}
\texttt{link}: Appears when a hyperlink is selected.
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-link-toolbar.png}
\caption{The link Styles toolbar lets you format hyperlinks in the
editor.}
\end{figure}
\texttt{table}: Appears when a table is selected.
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-table-toolbar.png}
\caption{The table Styles toolbar lets you format tables in the editor.}
\end{figure}
\texttt{text}: Appears when text is highlighted.
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-text-toolbar.png}
\caption{The text Styles toolbar lets you format highlighted text in the
editor.}
\end{figure}
Follow these steps to add a button to one of the Styles toolbars:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Inside the \texttt{populateConfigJSONObject()} method, retrieve the
Styles toolbar:
\begin{verbatim}
JSONObject stylesToolbar = toolbarsJSONObject.getJSONObject("styles");
if (stylesToolbar == null) {
stylesToolbar = JSONFactoryUtil.createJSONObject();
}
\end{verbatim}
\item
Retrieve the available selection toolbars:
\begin{verbatim}
JSONArray selectionsJSONArray = stylesToolbar.getJSONArray(
"selections");
\end{verbatim}
\item
Iterate through the selection toolbars, select the one you want to add
the button(s) to (\texttt{embedurl}, \texttt{image}, \texttt{link},
\texttt{table}, or \texttt{text}), retrieve the existing buttons, and
add your button. The example below adds the \texttt{clipboard}
plugin's \texttt{Copy}, \texttt{Cut}, and \texttt{Paste} buttons to
the \texttt{text} selection toolbar. Note that buttons are case
sensitive and may be aliased or not match the name of the plugin.
Search the plugin's
\href{/docs/7-1/reference/-/knowledge_base/r/ckeditor-plugin-reference-guide}{\texttt{plugin.js}
file} for \texttt{editor.ui.addButton} to find the button's name:
\begin{verbatim}
for (int i = 0; i < selectionsJSONArray.length(); i++) {
JSONObject selection = selectionsJSONArray.getJSONObject(i);
if (Objects.equals(selection.get("name"), "text")) {
JSONArray buttons = selection.getJSONArray("buttons");
buttons.put("Copy");
buttons.put("Cut");
buttons.put("Paste");
}
}
\end{verbatim}
The example above adds one of the
\href{/docs/7-2/reference/-/knowledge_base/r/ckeditor-plugin-reference-guide}{CKEditor
plugins bundled with Liferay DXP's AlloyEditor}. There are also
several buttons available by default with the AlloyEditor, but they
are not all enabled. The full list of existing buttons you can add to
the Styles toolbars is shown in the table below, ordered by Toolbar:
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
text | table | image | link |
---- | ----- | ----- | ---- |
bold | tableHeading | imageCenter | linkEdit |
code | tableRow | imageLeft | |
h1 | tableColumn | imageRight | |
h2 | tableCell | | |
indentBlock | tableRemove | | |
italic | | | |
link | | | |
ol | | | |
outdentBlock | | | |
paragraphLeft | | | |
paragraphRight | | | |
paragraphCenter | | | |
paragraphJustify | | | |
quote | | | |
removeFormat | | | |
strike | | | |
styles | | | |
subscript | | | |
superscript | | | |
twitter | | | |
ul | | | |
underline | | | |
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
See [here](https://alloyeditor.com/docs/features/camera.html) for an
explanation of each button's features.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\item
Update the AlloyEditor's configuration with the changes you made:
\begin{verbatim}
stylesToolbar.put("selections", selectionsJSONArray);
toolbarsJSONObject.put("styles", stylesToolbar);
jsonObject.put("toolbars", toolbarsJSONObject);
\end{verbatim}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy
your module} and create a new piece of content that uses the
AlloyEditor---such as a blog entry or web content article---to see
your new configuration in action!
\end{enumerate}
The \texttt{com.liferay.docs.my.button} module's updated text styles
toolbar is shown in the figure below:
\begin{figure}
\centering
\includegraphics{./images/alloyeditor-updated-styles-toolbar.png}
\caption{The Updated text styles toolbar lets you copy, cut, and paste
text in the editor.}
\end{figure}
\section{Related Topics}\label{related-topics-141}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-a-button-to-the-add-toolbar}{Adding
a Button to the Add Toolbar}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/ckeditor-plugin-reference-guide}{CKEditor
Plugin Reference Guide}
\end{itemize}
\chapter{Embedding Content in the
AlloyEditor}\label{embedding-content-in-the-alloyeditor}
Whether it's a video from a popular streaming service, or an
entertaining podcast, embedded content is commonplace on the web.
Sharing content from a third party is sometimes required to properly
cover a topic. The \texttt{EmbedProvider} mechanism lets you embed third
party content in the AlloyEditor, while writing blog posts, web content
articles, etc. By default, the \texttt{EmbedProvider} mechanism is only
configured for embedding video content (Facebook, Twitch, Vimeo, and
YouTube) into the AlloyEditor. This tutorial shows how to include
additional video providers, and even add support for additional content
types.
An \texttt{EmbedProvider} requires four pieces of information:
\begin{itemize}
\tightlist
\item
An ID: The content's ID
\item
A Template: The required embed code for the provider
\item
A URL Schemes: URL patterns that are supported for the provider
template
\item
A Type (optional): The provider category
\end{itemize}
When you add a supported URL to the editor, the \texttt{EmbedProvider}
transforms the URL into the embed code.
Follow these steps to create an \texttt{*EmbedProvider}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a module} for the Embed Provider.
\item
Add the following dependencies to the \texttt{build.gradle} file:
\begin{verbatim}
compileOnly group: "com.liferay", name:
"com.liferay.frontend.editor.api", version: "1.0.1"
compileOnly group: "com.liferay", name: "com.liferay.petra.string",
version: "2.0.0"
\end{verbatim}
\item
Create a component class that implements the
\texttt{EditorEmbedProvider} service:
\begin{verbatim}
@Component(
immediate = true,
service = EditorEmbedProvider.class
)
\end{verbatim}
\item
Optionally set the \texttt{type} property to the content's type. If
creating a provider for a content type other than video, you can
create a new type constant and add a new button for the content type.
If you do create your own button, we recommend that you use the
existing
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-editor/frontend-editor-api/src/main/java/com/liferay/frontend/editor/embed}{embed
video button's JSX files} as an example to write your own files. By
default, the provider is categorized as \texttt{UNKNOWN}. The example
configuration below specifies the \texttt{VIDEO} type, using a
constant provided by the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-api/src/main/java/com/liferay/frontend/editor/embed/EditorEmbedProviderTypeConstants.java}{\texttt{EditorEmbedProviderTypeConstants}
class}:
\begin{verbatim}
@Component(
immediate = true,
property = "type=" + EditorEmbedProviderTypeConstants.VIDEO,
service = EditorEmbedProvider.class
)
\end{verbatim}
\item
Implement the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-api/src/main/java/com/liferay/frontend/editor/embed/EditorEmbedProvider.java}{\texttt{EditorEmbedProvider}
interface}. An example configuration is shown below:
\begin{verbatim}
public class MyEditorEmbedProvider implements EditorEmbedProvider {
}
\end{verbatim}
\item
Add the required imports:
\begin{verbatim}
import com.liferay.frontend.editor.api.embed.EditorEmbedProvider;
import com.liferay.frontend.editor.api.embed.EditorEmbedProviderTypeConstants;
import com.liferay.petra.string.StringBundler;
\end{verbatim}
Note the \texttt{*TypeConstants} import is only needed if you're
adding a Video type provider.
\item
Override the \texttt{*EmbedProvider}'s \texttt{getId()} method to
return the ID for the provider. An example configuration is shown
below:
\begin{verbatim}
@Override
public String getId() {
return "providerName";
}
\end{verbatim}
\item
Override the \texttt{*EmbedProvider}'s \texttt{getTpl()} method to
provide the embed template code (usually an iframe for the provider).
The example below defines the template for a streaming video service.
Note that \texttt{\{embedId\}} is a placeholder for the unique
identifier for the embedded content:
\begin{verbatim}
@Override
public String getTpl() {
return StringBundler.concat(
"");
}
\end{verbatim}
\item
Override the \texttt{*EmbedProvider}'s \texttt{getURLSchemes()} method
to return an array of supported URL schemes that have an embedded
representation for the provider. URL schemes are defined using a
JavaScript regular expression that indicates whether a URL matches the
provider. Every URL scheme should contain a single matching group.
Matches replace the \texttt{\{embedId\}} placeholder defined in the
previous step:
\begin{verbatim}
@Override
public String[] getURLSchemes() {
return new String[] {
"https?:\\/\\/(?:www\\.)?liferaylunarresortstreaming.com\\/watch\\?v=(\\S*)$"
};
}
\end{verbatim}
\item
Deploy your module and open an app that uses the AlloyEditor, such as
Blogs, and create a new entry. Click the \emph{add button} and select
the video button---or your new content type button---and paste the
content's URL. Click the \emph{checkmark} to confirm that the URL
scheme is supported. The content is embedded into the editor.
\end{enumerate}
Now you know how to embed content in the AlloyEditor. Create a new
content entry, such as a blog post, and click the embed video
button---or the one you created---and paste the content's URL.
\section{Related Topics}\label{related-topics-142}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-buttons-to-alloyeditor-toolbars}{Adding
Buttons to AlloyEditor's Toolbars}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-new-behavior-to-an-editor}{Adding
New Behavior to an Editor}
\end{itemize}
\chapter{Adding New Behavior to an
Editor}\label{adding-new-behavior-to-an-editor}
You can select from several different WYSIWYG editors for your users,
and each is configurable and has its strengths and weaknesses.
Configuration alone, however, doesn't always expose the features you
want. In these cases, you can programmatically access the editor
instance to create the editor experience you want, using the
\texttt{liferay-util:dynamic-include} JavaScript extension point. It
injects JavaScript code right after the editor instantiation to
configure/change the editor.
\noindent\hrulefill
\textbf{Note:} By default, the CKEditor strips empty
\texttt{\textless{}i\textgreater{}} tags, such as those used for Font
Awesome icons, from published content, when switching between the Code
View and the Source View of the editor. You can disable this behavior by
using the \texttt{ckeditor\#additionalResources} or
\texttt{alloyeditor\#additionalResources}
\href{/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes}{extension
points} to add the code shown below to the editor:
\begin{verbatim}
CKEDITOR.dtd.$removeEmpty.i = 0
\end{verbatim}
\noindent\hrulefill
The \texttt{liferay-util:dynamic-include} extension point is in
configurable editors' JSP files: it's the gateway for injecting
JavaScript into your editor instance. In this article, you'll learn how
to use this JavaScript extension point. Follow these steps to inject
JavaScript into the WYSIWYG editor to modify its behavior:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a JS file containing your editor functionality in a folder that
makes sense to reference, since you must register the file in your
module. The extension point injects the JavaScript code right after
editor initialization.
Liferay injects JavaScript code for some applications:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/extension/creole_dialog_definition.js}{creole\_dialog\_definition.js}
for the wiki
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/extension/creole_dialog_show.js}{creole\_dialog\_show.js}
also for the wiki
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/extension/dialog_definition.js}{dialog\_definition.js}
for various applications
\end{itemize}
These JS files redefine the fields that show in dialogs, depending on
what the selected language (HTML, BBCode, Creole) supports. For
example, Creole doesn't support background color in table cells, so
the table cells are removed from the options displayed to the user
when running in Creole mode.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a module} that can register your new JS file and inject it into your
editor instance.
\item
Create a unique package name in the module's \texttt{src} directory,
and create a new Java class in that package. To follow naming
conventions, your class name should begin with the editor you're
modifying, followed by custom attributes, and ending with
\emph{DynamicInclude} (e.g.,
\texttt{CKEditorCreoleOnEditorCreateDynamicInclude.java}). Your Java
class should implement the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/servlet/taglib/DynamicInclude.html}{\texttt{DynamicInclude}}
interface.
\item
Directly above the class's declaration, insert the following
annotation:
\begin{verbatim}
@Component(immediate = true, service = DynamicInclude.class)
\end{verbatim}
This declares the component's implementation class and starts the
module once deployed to Portal.
\item
If you have not yet overridden the abstract methods from
\texttt{DynamicInclude}, do that now. There are two implemented
methods to edit: \texttt{include(...)} and \texttt{register(...)}.
\item
In the \texttt{include(...)} method, retrieve the bundle containing
your custom JS file. Retrieve the JS file as a URL and inject its
contents into the editor. Here's the code that does this for the
\texttt{creole\_dialog\_definition.js} file:
\begin{verbatim}
Bundle bundle = _bundleContext.getBundle();
URL entryURL = bundle.getEntry(
"/META-INF/resources/html/editors/ckeditor/extension" +
"/creole_dialog_definition.js");
StreamUtil.transfer(entryURL.openStream(), response.getOutputStream());
\end{verbatim}
In the \texttt{include(...)} method, you can also retrieve editor
configurations and choose the JS file to inject based on the
configuration selected by the user. For example, this would be
applicable for the use case that was suggested previously dealing with
Creole's deficiency with displaying background colors in table cells.
Liferay implemented this in the \texttt{include(...)} method in the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/java/com/liferay/frontend/editor/ckeditor/web/internal/servlet/taglib/CKEditorCreoleOnEditorCreateDynamicInclude.java}{\texttt{CKEditorCreoleOnEditorCreateDynamicInclude}}
class.
\item
Make sure you've instantiated your bundle's context so you can
successfully retrieve your bundle. As a best practice, do this by
creating an activation method and then setting the
\texttt{BundleContext} as a private field. Here's an example:
\begin{verbatim}
@Activate
protected void activate(BundleContext bundleContext) {
_bundleContext = bundleContext;
}
private BundleContext _bundleContext;
\end{verbatim}
This method uses the \texttt{@Activate} annotation, which specifies
that it should be invoked once the service component has satisfied its
requirements. For this default example, the \texttt{\_bundleContext}
was used in the \texttt{include(...)} method.
\item
Now register the editor you're customizing. For example, if you were
injecting JS code into the CKEditor's JSP file, the code would look
like this:
\begin{verbatim}
dynamicIncludeRegistry.register(
"com.liferay.frontend.editor.ckeditor.web#ckeditor#onEditorCreate");
\end{verbatim}
This registers the CKEditor into the Dynamic Include registry and
specifies that JS code will be injected into the editor once it's
created.
Just as you can configure individual JSP pages to use a specific
implementation of the available WYSIWYG editors, you can use those
same implementation options for the registration process. Visit the
\href{https://docs.liferay.com/dxp/portal/7.1-latest/propertiesdoc/portal.properties.html\#Editors}{Editors}
section of \texttt{portal.properties} for more details. For example,
to configure the Creole implementation of the CKEditor, you could use
the following key:
\begin{verbatim}
"com.liferay.frontend.editor.ckeditor.web#ckeditor_creole#onEditorCreate"
\end{verbatim}
\end{enumerate}
That's it! The JS code that you created is now injected into the editor
instance you've specified. You're now able to use JavaScript to add new
behavior to your Liferay DXP supported WYSIWYG editor!
\section{Related Topics}\label{related-topics-143}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-new-behavior-to-an-editor}{Adding
New Behavior to an Editor}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes}{Embedding
Portlets in Themes}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}
\end{itemize}
================================================
FILE: book/developer/publish.tex
================================================
\chapter{Publishing Your App}\label{publishing-your-app}
As you develop apps, you may want to sell them to or share them with
consumers. You can distribute your apps on the
\href{http://marketplace.liferay.com}{Liferay Marketplace}. The process
is straightforward, but must make some decisions along the way and
prepare your app for publishing on the Marketplace.
This section's first tutorial explains Liferay Marketplace-related
concepts and informs you of decisions to make before starting the
publishing process from your portal. Then you'll learn how to get
resources, such as your app's icon, screenshots to show off your app,
and a comprehensive description of your app, ready for the publishing
process. After that, you'll learn the step-by-step process of submitting
your app to the Marketplace. Lastly, you'll learn how to monitor your
app's success on Liferay Marketplace and modify or add new versions of
your app to the Marketplace.
\chapter{Planning Your App's
Distribution}\label{planning-your-apps-distribution}
When you start the formal process of submitting your app to the
Marketplace, in addition to uploading your app's files you must answer a
host of important questions. For example, you must clarify who owns the
app, specify pricing for the app, define its licensing scheme (if it's a
paid app), associate a person or company as its owner and maintainer,
and specify the versions of Liferay that the app supports. Your answers
to these questions help you determine if you must package multiple
versions of the app. This tutorial prepares you by explaining the
questions and ways you might answer them. Here's what's covered:
\begin{itemize}
\tightlist
\item
\href{/how-to-publish/-/knowledge_base/publish/selling-your-app-or-making-it-free}{Selling
your app or making it free}
\item
\href{/how-to-publish/-/knowledge_base/publish/publishing-as-an-individual-or-on-behalf-of-a-company}{Publishing
as an individual or on behalf of a company}
\item
\href{/how-to-publish/-/knowledge_base/publish/licensing-and-pricing-your-app}{Licensing
and pricing your app}
\item
\href{/how-to-publish/-/knowledge_base/publish/targeting-liferay-editions-and-versions}{App
versioning and target Liferay editions and versions}
\end{itemize}
The first tutorial answers this question: Should I sell my app or make
it free?
\section{Selling Your App or Making it
Free}\label{selling-your-app-or-making-it-free}
Do you want to sell your app on the Marketplace? Or do you want to share
it freely with anyone on the Marketplace? It's up to you. Most of the
content that follows describes options for paid apps (apps you sell).
If you're selling your app, you must publish using a \emph{Paid App
Account}. You must also specify licensing, a price structure, and
regional availability for your paid apps.
Importantly, you can't change the app from free to paid or from paid to
free once the app is published to the Marketplace. Offering the app in
in both license types requires submitting another app under a different
name (title). If you want both free and paid licenses for your app, you
must submit the app under one name for free licenses and under another
name for paid licenses. Make sure you select the license type (i.e.,
free or paid) that's best for your app.
Have you decided who to list as the app's author/owner? Have you decided
manages the app once it's on the Marketplace? App ownership options are
explained next.
\section{Publishing as an Individual or on Behalf of a
Company}\label{publishing-as-an-individual-or-on-behalf-of-a-company}
You can publish an app as yourself (an individual) or on behalf of a
\emph{company}. This determines who is shown as the app's author and
owner. Your selection also determines who can access the app behind the
scenes, once it's published.
The default option is publishing on behalf of yourself. If you go with
this option, your name shows as the app's author/owner in the
Marketplace. The term \emph{personal app} refers to an app published by
an individual. That individual is the only one who can manage the
personal app. Managing an app includes such duties as adding new
releases to it, adding new versions of it, and editing its details.
Publishing on behalf of a company effectively hands the keys over to the
company's administrators. The app shows on the company's Marketplace app
development page and in the company's list of apps on the company's
public profile page. Company admins have the same permission that an
individual author has to manage the app (add new releases, new versions,
edit details, etc.). The company's name alone is shown as the app's
author/owner.
You can
\href{https://www.liferay.com/marketplace/become-a-developer}{register}
yourself as a Marketplace Developer or one of your company's
administrators can register the company as a Marketplace Developer. You
can either register for a Free Basic Account or register for a Paid App
Developer Account. A Paid App Developer Account lets you submit paid
apps to the Liferay Marketplace for sale to customers globally. It
enables you to monetize the benefits of the Marketplace and the benefits
of the Liferay distribution channel. The best part is, you can upgrade
to a Paid App Developer Account if and when you want to submit a paid
app to the Marketplace! Here's a comparison of the Marketplace developer
account options:
Free Basic Account
No cost to register as a Marketplace developer
Distribute free apps on the Marketplace
Access to developer-only resources (e.g., Liferay Dev Studio, Liferay
Developer License)
Participate in Marketplace promotions
Converting from Basic-to-Paid
When you're ready to submit a paid app to the Marketplace:
Agree to the Developer Agreement
Pay \$99 annual fee
Submit relevant tax documents (eg., W-9, W-8BEN) to receive payments
Enter PayPal info to receive payments (PayPal account must be a Verified
Business Account)
Paid App Developer Account
Upgrade when you're ready: only pay annual fee at time of paid app
submission
Offer paid licensing/support for your apps
Receive 80\% of app sales proceeds
Access to detailed transaction history
Paid apps customer management
All the benefits of a Basic Developer Account
Now that you've determined your app's owner and you've registered an
account to manage the app, you can learn about licensing options for
paid apps.
\section{Licensing and Pricing Your
App}\label{licensing-and-pricing-your-app}
You have significant control over how to price your app. You choose the
license term (perpetual vs.~annual), choose the license type (standard
vs.~developer), define a pricing structure (pricing and bundled
discounting based on a license unit), and specify regional availability.
Even after your app is on the Marketplace, you can tweak general pricing
or modify regional pricing. Here you'll learn how to do all these
things.
\subsection{Determining a License
Term}\label{determining-a-license-term}
Here are the license term options:
\begin{itemize}
\item
Perpetual license: does not expire.
\item
Non-perpetual license: must be renewed annually.
\end{itemize}
Importantly, you can't change your app's license terms once the app is
approved. Releasing an approved app under a different license term
requires submitting another app under a new name (title). So make sure
to think through the license term that makes the most sense for your
app.
\noindent\hrulefill
\textbf{Note:} If you are a foreign developer based outside of the
United States, non-perpetual license sales (considered to be royalty
income) to US customers are subject to a 30\% withholding tax. This tax
does not apply to perpetual licenses. For more information on licensing
terms and fees, please refer to the \href{/faq}{FAQ}.
\noindent\hrulefill
\subsection{Determining License Type and License Unit
Pricing}\label{determining-license-type-and-license-unit-pricing}
Licenses are set to run on a permitted number of Instance Units (defined
as a single installation of the Liferay, which corresponds to one (1)
Liferay \texttt{.war} file). You can create tiers of bundled options to
accommodate the number of Instance Units to offer to customers. You can
also offer a discounted price for a bundle of multiple Instance Units.
There are two types of licenses that you can offer for your app:
standard licenses and developer licenses. Standard licenses are intended
for production server environments. Developer licenses are limited to 10
unique IP addresses and, therefore, should not be used for full-scale
production deployments.
\begin{figure}
\centering
\includegraphics{./images/licenseview.png}
\caption{Liferay Marketplace lets you specify different types of
licenses and license quantities for pricing.}
\end{figure}
You can also offer subscription services or 30-day trials. Support,
maintenance, and updates should comprise subscription services.
Depending on how you decide to license your app, you have a few options
to consider with regard to subscription services:
\textbf{If you're offering a perpetual license\ldots{}}
You can offer annually renewable subscription services. During the app
submission process if you choose to \emph{offer subscription services},
you're asked to price subscription services on a \emph{per Instance Unit
per year} basis. The first year of subscription services is included
with a perpetual license. If a customer wants to continue receiving
services after the first year, the customer must renew subscription
services at the price you designate. Customers are entitled to support,
maintenance, and updates as long as they continue to annually renew
subscription services.
If you choose not to offer subscription services, customers are entitled
to only app updates, if and when updates become available. This type of
one-time, upfront payment model may work well for less complex apps and
themes/templates.
\textbf{If you're offering a non-perpetual (annual/renewable)
license\ldots{}}
You can offer annually renewable subscription services. During the app
submission process if you choose to \emph{offer subscription services},
you should build the price of subscription services into the annual
price of the non-perpetual license; please take this into account as you
price non-perpetual licenses. In effect, the customer pays one price
annually for both the app license and subscription services. The
customer is entitled to support, maintenance, and updates as long as
they continue to renew their non-perpetual license.
If you choose not to offer subscription services, customers are entitled
to only app updates if and when updates become available. They can
receive updates as long as they continue to have valid non-perpetual
licenses.
\subsection{Setting Prices for License Options by
Region}\label{setting-prices-for-license-options-by-region}
You can specify countries your app will be available in and the app's
price in in each of those countries. You can make it as simple (single
price offered globally) or as granular (different price in each country
offered) as you want.
\begin{figure}
\centering
\includegraphics{./images/pricing_screenshot.png}
\caption{Liferay Marketplace lets you specify prices for all of your
license options and lets you specify them by region.}
\end{figure}
Choose the currency to use with your pricing options. Decide on the
renewal cost for any support services you offer. The support services
price is per instance, so if you specify \$100 USD and the customer is
running 10 instances, their annual support services renewal cost will be
\$1000. Note: This only applies to perpetual licenses. For non-perpetual
licenses, include any support services cost in the annual license price.
Even after an app has been approved, you can change the currency type
and currency price of its license bundles, and you can modify regional
availability of license bundles.
Although Liferay Marketplace supports major currencies and a broad list
of countries, not all currencies and countries are currently available.
Additional currencies and countries may become available at a later
time.
\subsection{Considering the Liferay Marketplace
Fee}\label{considering-the-liferay-marketplace-fee}
By selling your paid apps on the Liferay Marketplace, you're agreeing to
share app sales revenue with Liferay. For each app sale, you receive
80\% of the sales proceeds and Liferay receives 20\% of the sales
proceeds. We believe this type of fee structure is extremely competitive
compared to other app marketplaces. Liferay uses its share of app sale
proceeds to:
\begin{itemize}
\tightlist
\item
Operate and improve the Liferay Marketplace.
\item
Provide developer services, such as payment processing, license
tracking, and performance metrics.
\item
Continue investing in and growing the Liferay ecosystem.
\end{itemize}
\textbf{Comparison of Liferay's App Revenue Sharing with that of Other
App Marketplaces}
Share to Developer
Liferay Marketplace
80\%
Atlassian Marketplace
75\%
DNN Store
75\%
Concrete 5
75\%
Apple App Store
70\%
As you can see, Liferay's fee is reasonable.
You need a valid PayPal account to receive payment; you're asked to
provide this info when registering for or converting to a Paid App
Developer Account. Payments are issued no later than 90 days after the
transaction.
Now that you've decided on licensing options and pricing, you can
concentrate on the Liferay versions your app runs on.
\section{Targeting Liferay Editions and Versions and Versioning your
App}\label{targeting-liferay-editions-and-versions-and-versioning-your-app}
There are multiple versions of Liferay and multiple editions (e.g.,
community and enterprise) for each version. You must decide the versions
and editions to build your app on. And lastly, you must decide how to
version your app. This tutorial covers these topics.
\subsection{Determining Editions and Versions of Liferay to
Target}\label{determining-editions-and-versions-of-liferay-to-target}
Of course, targeting the widest possible range of Liferay editions and
versions in an app typically draws larger audiences to the app. And
there may be certain features in these editions and versions that you
want to take advantage of. In your app's plugin
\href{/how-to-publish/-/knowledge_base/publish/preparing-your-app}{packaging
properties}, specify packaging directives to indicate the editions the
app supports and the version that the app supports. To ensure the widest
audience for your app, make your app compatible with both Liferay
Digital Experience Platform (DXP) and Liferay Portal Community Edition
(CE).
You can prepare a set of app files (including its
\texttt{liferay-plugin-package.properties} file) for each version of
Liferay you want to support. When uploading your app, you can specify
which versions of Liferay your app is compatible with and you can
appropriately upload the sets of app files that are designed for those
different versions. The next article in this guide explains how to go
about
\href{/how-to-publish/-/knowledge_base/publish/preparing-your-app}{specifying
packaging directives}.
Note that apps on Liferay Marketplace must be designed for Liferay
Portal 6.1 GA3 or later. That doesn't mean they can't work with prior
versions. However, only Liferay Portal 6.1 GA3 and later versions
support installing apps directly from Marketplace and provide safeguards
against malicious apps. If you want to use an app with an earlier
version of Liferay DXP, make sure that version provides the dependencies
your app needs.
Lastly, you should determine a versioning scheme for your app. How will
you refer to the first version of your app, the second version, and so
on.
\subsection{Decide on a Versioning
Scheme}\label{decide-on-a-versioning-scheme}
A version of an app represents the functionality of the app at a given
point in time. When you first create an app, you give it an initial
version (e.g., \texttt{1.0}). On updating the app, you increment its
version (e.g., from \texttt{1.0} to \texttt{1.1}), and you upload new
files representing that version of the app. In some cases, you specify
additional qualifiers in order to convey a special meaning. For example,
you may declare that the version of your app is always in x.y.z format
(where you've clearly defined the significance of x, y, and z). Liferay
versions and official Liferay app versions use this format.
In any case, you're free to assign your app's version designators any
way you like. We recommend that you stick to a well known and easily
understandable format, such as \texttt{1.0}, \texttt{1.1}, \texttt{1.2},
and so on. Although you may want to include alphabetical characters
(e.g., \texttt{1.0\ Beta\ 2} or \texttt{6.3\ Patch\ 123235-01}), we
discourage it, as such characters may confuse customers as to how your
app's versions relate to one another.
Keep in mind that the releases of Liferay with which your app works must
be specified using Liferay's versioning scheme, as explained in
\href{/docs/7-2/deploy/-/knowledge_base/d/understanding-liferays-releases}{Understanding
Liferay's Releases}. See the later section \emph{Specify App Packaging
Directives} for details on specifying the releases of Liferay for which
your app is designed.
Congratulations on coming up with a sound game plan for your app! The
next articles explain how to prepare apps for publishing.
\chapter{Preparing Your App}\label{preparing-your-app}
As a Liferay developer, you're undoubtedly already familiar with the
concept of plugins (portlets, themes, etc.). If you're not familiar with
Liferay plugins, see \href{/docs/7-2/appdev}{Introduction to Liferay
Development}. A \emph{Liferay App} (sometimes just called an \emph{app})
is a collection of one or more of these plugins, packaged together to
represent the full functionality of an application on the Liferay
platform. In addition to the plugins contained within an app, apps have
metadata such as names, descriptions, versions, and other information
that describe and track the app throughout its life cycle.
Much like standard Liferay plugins, Liferay apps are also
auto-deployable. Liferay Marketplace apps are distributed via a special
file type with an \texttt{.lpkg} extension. To deploy these files, drop
them into a running Liferay instance's auto-deploy folder
(\texttt{{[}Liferay\_Home{]}/deploy}), like any other plugin.
As an app developer, you're not required to create the \texttt{.lpkg}
files. Instead, your app's individual plugins (WAR files for traditional
plugins or JAR files for OSGi modules) are uploaded as part of the
publication process, along with information (name, description, version,
icon, etc.) that identifies the app. The publication process is
described in detail later.
At this point in preparing to publish your app, you've developed your
app. But before you start the formal publishing process, you must
prepare your app's files and app metadata.
\section{Marketplace App Metadata
Guidelines}\label{marketplace-app-metadata-guidelines}
The following app metadata guidelines ensure that apps are submitted
with important and necessary supporting information. The metadata that
you submit with your app serves both as necessary information for your
app's buyers (e.g., your contact info) and as promotional assets (e.g.,
description, screenshots, etc.) that can help drive traffic to and
downloads of your app!
\begin{figure}
\centering
\includegraphics{./images/dev-portal-app-metadata-guidelines.png}
\caption{Check out how good your app can look on the Marketplace.}
\end{figure}
Think of a good name and description for your app. If you haven't
already done so, take some screenshots, design an icon, and create a
website for your app. The table below helps you address the Marketplace
app metadata requirements and produce an appealing app advertisement.
\textbf{Marketplace App Metadata Guidelines:}
Required Metadata
Description
App Name
This is probably the most important branding element of your app, so be
creative! Some important things to keep in mind:
In some views within the Marketplace, app titles longer than 18
characters are shortened with an ellipsis.
Titles must not be longer than 50 characters.
App title may contain the word ``Liferay'' to describe its use or intent
as long as the name does not imply official certification or validation
from Liferay, Inc. (For example, names such as ``Exchange Connector for
Liferay'' or ``Integration Connector Kit for Liferay'' would be allowed,
while ``Liferay Mail Portlet,'' ``Liferay Management Console,'' or
``Liferay UI Kit'' would not be permissible without explicit approval).
Please refer to our trademark policy for details.
Please try to conform the app name as closely as possible to the actual
plugin (portlet, module, theme, etc.) name.
Support Information
Please include an email address, contact information, and/or website URL
for the ``Support'' field. If there's an issue with your app, they
should be a way to contact you.
If you choose not to offer Support Services (by unchecking ``Offer
Support'' during the app submission process), you must provide support
contact information so that buyers can ask you general questions about
your app.
Description
Using English as the default language, you must provide a description
for your app. After providing the description in English, you can
provide other translations of the text.
At a minimum, the description should provide a concise overview of what
the app does. Great descriptions also list key functionality and what
customers can expect to gain by deploying your app. For a good
description example, see the Audience Targeting app on the Marketplace.
Specify any plugin dependencies (plugins that must be installed prior to
running your app) and environment compatibilities (compatibility with
specific app servers) here, so that potential buyers and the Liferay app
review team are aware of these requirements.
What's New?*
Using English as the default language, describe what's new and improved
in your app. After this, you can provide other translations of the text.
This field is shown on updating an app that you've already submitted,
regardless of whether the app has been published.
Compatibility with Future Minor Releases of Liferay
Please include a ``+'' at the end of the latest version when specifying
version constraints in your liferay-plugin-package.properties file
(e.g., ``liferay-versions=7.2.1+, 7.2.20+''). This ensures that the app
continues to be deployable to future Liferay DXP versions within a minor
release. If, in the future, you discover your app does NOT work with a
particular version, you can modify the list to exclude that version.
Increase Your Potential User Base
In most cases, an app compatible with Liferay Portal CE also runs on
Liferay Digital Experience Platform (DXP) (or Liferay Portal EE), and
vice versa. Specifying compatibility with both DXP/EE and CE versions of
Liferay ensures a wider audience for your app!
You can request a Developer License to support testing and confirm
compatibility.
Icon
App icons must be exactly 90 pixels in both height and width in PNG,
JPG, or GIF format. The image size cannot exceed 512kb. Animated images
are prohibited.
The use of the Liferay logo, including alternate versions of the Liferay
logo, is permitted only with Liferay's explicit approval. Please refer
to our trademark policy for details.
Screenshots
App screenshots must not exceed 1080 pixels in width x 678 pixels in
height and must be in the JPG format. Each screenshot's file size must
not exceed 384KB. Each screenshot should be the same size (each is
automatically scaled to match the aspect ratio of the above dimensions),
and it is preferable to name screenshots sequentially, for example
fluffy-puppies-01.jpg, fluffy-puppies-02.jpg, and so on.
Make sure your icons, images, descriptions, and tags are free of
profanity or other offensive material.
During the publication process, you upload your app's individual plugin
WAR files and module JAR files along with the app's metadata (name,
description, version, icon, etc.).
\subsection{Additional Requirements for Themes/Site
Templates}\label{additional-requirements-for-themessite-templates}
Themes and Site Templates must include sample content and optionally
link to a demo website that provides context. This ensures a uniform
experience for Marketplace users: they download a Theme or Site Template
from Marketplace, install it, go to Sites or Site Templates in the
Control Panel, and immediately see the Theme or Site Template in action.
\noindent\hrulefill
\textbf{Important:} Demo sites must be valid. Demo website, Theme, and
Site Template sample content must not plagiarize or infringe on
copyrights.
\noindent\hrulefill
The
\href{/docs/7-2/frameworks/-/knowledge_base/f/importing-resources-with-a-theme}{Resources
Importer} includes files and web content that imports automatically into
Liferay on Theme and Site Template deployment. Use the Resources
Importer to include files/web content to provide a sample context for
your Theme or Site Template. The
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes
tutorials} provide details.
\section{Packaging Your Marketplace
App}\label{packaging-your-marketplace-app}
Liferay apps are ``normal'' Liferay plugins with additional information
about them. Therefore, most of the requirements are the same as those
that exist for other Liferay plugins, as explained in the tutorials on
creating
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC Portlets}.
In addition to those requirements, there are some Marketplace-specific
ones to keep in mind:
\begin{itemize}
\item
\textbf{Target the Appropriate Java JRE}: Regardless of the tools you
use to develop your app, your app's bytecode must be compatible with
the target Java JRE for your version of Liferay. Apps are rejected if
their bytecode is not compatible with the Java JRE for the intended
version of Liferay. In the Compatibility Matrix for your Liferay
version, make sure your app's bytecode is compatible with the latest
certified Java JDK version.
If you use any Gradle-based build environment like
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}, make sure your \texttt{targetCompatibility} is set to the
latest certified Java JDK version.
Similarly, if you use Maven, you can set the bytecode compatibility
like this:
\end{itemize}
\begin{verbatim}
1.8
1.8
\end{verbatim}
If you use the
\href{/docs/6-2/tutorials/-/knowledge_base/t/plugins-sdk}{Liferay
Plugins SDK} to develop your app for Liferay Portal 6.2 or 6.1, you can
set the Java version by overriding the \texttt{ant.build.javac.target}
property in the Plugins SDK's \texttt{build.properties} file.
\begin{itemize}
\tightlist
\item
\textbf{WAR (\texttt{.war}) files}:
\begin{itemize}
\item
WAR files must contain a
\texttt{WEB-INF/liferay-plugin-package.properties} file.
\item
WAR files must not contain any
\texttt{WEB-INF/liferay-plugin-package.xml} file.
\item
WAR file names must not contain any commas.
\item
WAR file names must conform to the following naming convention:
\emph{context\_name}\texttt{-}\emph{plugin\_type}\texttt{-A.B.C.D.war}
Where:
\item
\emph{context\_name}: Alpha-numeric (including \texttt{-} and
\texttt{\_}) short name of your app. This name is used as the
deployment context, and must not duplicate any other app's context
(you'll be warned if you use a context name of any other app on the
Marketplace).
\item
\emph{plugin\_type}: one of the following: \texttt{hook},
\texttt{layouttpl}, \texttt{portlet}, \texttt{theme}, or
\texttt{web}.
\item
\texttt{A.B.C.D}: The four digit version of your WAR file. Four
digits must be used.
Example: \texttt{myapp-portlet-1.0.0.0.war}
\end{itemize}
\item
\textbf{\texttt{WEB-INF/}\href{https://docs.liferay.com/ce/portal/7.1-latest/propertiesdoc/liferay-plugin-package_7_1_0.properties.html}{\texttt{liferay-plugin-package.properties}}
file:}
\begin{itemize}
\tightlist
\item
Property \texttt{recommended.deployment.context} must not be set.
\item
Setting property \texttt{security-manager-enabled} to \texttt{true}
is mandatory for all paid apps that contain WAR-style plugins and
that are for 6.1 EE/CE GA3 and 6.2 EE/CE; the setting is optional
for free apps. Setting this property to \texttt{true} enables
Liferay's Plugin Security Manager. If you're enabling the security
manager, you must also define your Portal Access Control List (PACL)
in this file. Read
\href{/docs/6-2/tutorials/-/knowledge_base/t/plugin-security-and-pacl}{Plugins
Security and PACL} for information on developing secure apps.
\end{itemize}
\item
\textbf{Deployment contexts}:
\begin{itemize}
\tightlist
\item
Liferay reserves the right to deny an application if any of its
plugin deployment contexts is the same as a context of another
plugin in the Marketplace.
\item
Liferay reserves the right to replace app plugin WAR files that have
the same deployment context as plugins built by Liferay.
\end{itemize}
\end{itemize}
There are some additional requirements for uploading Liferay DXP 7.x and
Liferay Portal CE 7.x apps:
\begin{itemize}
\tightlist
\item
\textbf{OSGi modules in JAR (\texttt{.jar}) files}:
\begin{itemize}
\tightlist
\item
The manifest file must at minimum contain the following manifest
headers:
\begin{itemize}
\tightlist
\item
\texttt{Bundle-SymbolicName}: a unique name for the module
\item
\texttt{Bundle-Version}: the module's version
\item
\texttt{Web-ContextPath}: the servlet context path
\end{itemize}
\end{itemize}
\item
\textbf{Apps containing multiple file types:}
\begin{itemize}
\tightlist
\item
Liferay DXP 7.x and Liferay Portal CE 7.x apps can contain a mix of
WAR-based plugins and OSGi JAR-based plugins. Regardless of file
type, each plugin must be able to run on Liferay DXP 7.x and Liferay
Portal CE 7.x.
\end{itemize}
\end{itemize}
\noindent\hrulefill
\textbf{Important:} If you're developing a paid app or want your free
app to satisfy Plugin Security Manager on Liferay 6.2 or 6.1 GA3, make
sure to specify PACLs for your traditional WAR-style plugins. See the
article
\href{/docs/6-2/tutorials/-/knowledge_base/t/plugin-security-and-pacl}{Plugin
Security and PACL} for details. Give yourself adequate time to develop
your app's permission descriptors and time to test your app thoroughly
with the security manager enabled.
\noindent\hrulefill
Apps usually consist of multiple plugins (e.g., multiple WAR or JAR
files) and plugin types. In addition, you may want to consider how to
package your app for running on different Liferay versions.
\subsection{Considering Package Variations to Target Different Versions
of
Liferay}\label{considering-package-variations-to-target-different-versions-of-liferay}
Apps can be written to work across many different versions of Liferay.
For example, suppose you want to publish version 1.0 of your app, which
you're supporting on Liferay Portal 6.1 and 6.2. Due to
incompatibilities between these Liferay versions, it may be impossible
to create a single binary WAR or JAR file that works across both Liferay
versions. In this case, you must compile your app twice: once against
Liferay Portal 6.1 and once against 6.2, producing two different
\emph{packages} (also called variations) of your version 1.0 app. Each
package has the same functionality, but they're different files. You can
upload such packages to support your app on different Liferay versions.
Packages are sometimes referred to as files that make up your app.
Now that you've prepared your app's files and specified its metadata,
it's time to submit it to Liferay for publishing on the Marketplace!
\chapter{Submitting Your App}\label{submitting-your-app}
Now that you have everything in order for publishing your app, you can
start the Marketplace app submission process.
Go to your \emph{Account Home} on
\href{http://www.liferay.com}{liferay.com}. In the left side navigation
panel of your profile page, there are links to pages related to using
apps and developing apps. Links to \emph{Apps} and \emph{Metrics} appear
in the \emph{Development} section of the navigation panel. You'll use
these links heavily during development, so you may want to bookmark this
page too. Click \emph{Apps} from the \emph{Development} section to
access your app development page.
\begin{figure}
\centering
\includegraphics{./images/marketplace-my-app-manager.png}
\caption{Your app development page lists the apps you've developed and
enables you to add new apps for publishing to the Marketplace.}
\end{figure}
Now that you know how to get to your app development page, you can
submit your app! It involves,
\begin{itemize}
\tightlist
\item
\href{/how-to-publish/-/knowledge_base/publish/specify-your-apps-initial-details}{Specifying
your app's initial details}
\item
\href{/how-to-publish/-/knowledge_base/publish/uploading-your-app}{Uploading
your app}
\item
\href{/how-to-publish/-/knowledge_base/publish/creating-your-licensing-and-pricing-model}{Creating
your licensing and pricing model}
\item
\href{/how-to-publish/-/knowledge_base/publish/submitting-your-app-for-review}{Submitting
your app for review}
\end{itemize}
To begin the process of publishing your app, click \emph{Add New App}.
The app wizard appears, so that you can fill in your app's details.
\section{Specifying Your App's Initial
Details}\label{specifying-your-apps-initial-details}
From this screen you enter your app's basic details. Previous articles
\href{/how-to-publish/-/knowledge_base/publish/planning-your-apps-distribution}{Planning
Your App's Distribution} and
\href{/how-to-publish/-/knowledge_base/publish/preparing-your-app}{Preparing
Your App} helped prepare you for filling in these details.
The top portion of the app wizard's initial screen is shown in the
figure below.
\begin{figure}
\centering
\includegraphics{./images/marketplace-add-app-details.png}
\caption{The app wizard lets you add details about your app, an icon,
and screen shots. Scroll down further to see options for specifying
relevant URLs, adding tags, and specifying the editions of Liferay that
your app supports.}
\end{figure}
Here are descriptions of this screen's fields:
\textbf{App Owner:} Choose to whom the app ``belongs'' once it is
uploaded--either yourself (personal) or a company. If you'd like to
submit on behalf of a company, click \emph{Create a company} to go to
the \emph{Join A Company} page. From this page, search to see if your
company already exists in the Liferay Marketplace. If you wish to
publish your app on behalf of your company, but your company does not
yet have a Marketplace profile, register your company by clicking
\emph{Register My Company}, filling out the registration form, and
submitting the form.
\textbf{App Pricing:} Choose whether you want the app to be free or
paid. If you choose paid, you'll have the option to specify pricing and
licensing details later in the submission process.
Importantly, you can't change the app from free to paid or from paid to
free once the app is published to the Marketplace. In order to offer the
app in the other license type, you must submit another app under a
different name (title). If you wish to have both free and paid licenses
for your app, you must submit the app under one name for free licenses
and submit it under another name for paid licenses. Make sure to select
the license type (i.e., free or paid) that's best for your app.
\textbf{Title:} Name of your application.
\textbf{Description:} Like the name says, this is a description of your
app. You can put anything you want here, but a good guideline is no more
than 4-5 paragraphs. This field does not allow any markup tags or other
textual adornments--it's just text.
\textbf{Localized:} Selecting this displays an \emph{Add Translation}
button that lets you denote translations your app provides.
\textbf{Icon:} Upload an icon image to represent your app.
\textbf{Screen Captures:} Upload one or more screenshots of your app.
The screenshots you upload here are displayed in a carousel of rotating
images when your app is viewed on the Marketplace.
\textbf{Category:} Choose the Marketplace category that most accurately
describes what your app does. Users looking for specific types of apps
often browse categories by clicking on a specific category name in the
main Marketplace home page. Having your app listed under the appropriate
category helps them find your app.
\textbf{Developer Website URL:} This is a URL that should reference the
web site associated with the development of the app. For open source
apps, this typically points at the project site where the source is
maintained. For others, links to a home page of information about this
app would be appropriate.
\textbf{Support URL:} This is a URL that should reference a location
where your app's purchasers or users can get support.
\textbf{Documentation URL:} What better way to showcase the amazing
capabilities of your app than to have a live, running version of it for
potential buyers to see and use? This field can house a URL pointing to
something as exciting as that and/or documentation for using your app.
\textbf{API Reference URL:} This is a URL that should reference your
app's API documentation (e.g., Javadoc).
\textbf{Source Code URL:} If you'd to provide a link to the source code
of your app, do so here.
\textbf{Labs:} You can denote an app as experimental by checking the
appropriate box.
\textbf{Security:} If your app is only for Liferay 7.0 or later, or does
not contain WAR-style plugins, or does \emph{not} use Liferay's PACL
Security Manager, check the appropriate box. Otherwise, enable the
security manager in your app by including the setting
\texttt{security-manager-enabled=true} in the
\href{http://docs.liferay.com/portal/6.2/propertiesdoc/liferay-plugin-package_6_2_0.properties.html}{\texttt{liferay-plugin-package.properties}}
file in your plugin WAR files.
\textbf{Tags:} A set of descriptive words that categorize your app.
These tags are free-form and can help potential purchasers find your app
through keyword searches, tag clouds, and other search mechanisms. You
can click on \emph{Suggestions} to let Marketplace suggest some tags for
you based on the data you've already entered. Click \emph{Select} to
select from existing tags, or you can manually type in new tags.
\textbf{EULA:} You can use the default end user license agreement (EULA)
or provide your own. There's a link to the minimum terms which custom
EULAs must satisfy.
Remember to visit the articles
\href{/how-to-publish/-/knowledge_base/publish/planning-your-apps-distribution}{Planning
Your App's Distribution} and
\href{/how-to-publish/-/knowledge_base/publish/preparing-your-app}{Preparing
Your App} for information to help you decide how best to fill in many of
these fields.
Once you've entered all your app's details, click \emph{Next} to get to
the screen for uploading your app's files.
\section{Uploading Your App}\label{uploading-your-app}
On this screen, you must specify your app's version and upload your
app's files. Note that the article
\href{/how-to-publish/-/knowledge_base/publish/planning-your-apps-distribution}{Planning
Your App's Distribution} helps you plan your app's versioning scheme.
Likewise, the article
\href{/how-to-publish/-/knowledge_base/publish/preparing-your-app}{Preparing
Your App} helps you determine which files to upload.
To start uploading, click \emph{Browse} and select the files that make
up your app. Each time you add a file, it automatically begins to upload
and its compatibility information is scanned. You must upload at least
one file before advancing beyond this screen. Once the files are
successfully uploaded, a check mark appears next to each plugin, and the
plugins are displayed based on their compatibility information.
\begin{figure}
\centering
\includegraphics{./images/marketplace-app-version-and-upload-files.png}
\caption{Specify a set of files for each Liferay version you wish to
support.}
\end{figure}
If you selected \emph{Free} for your app pricing, click \emph{Next} to
advance to the final screen. If you selected \emph{Paid}, you'll be
presented with additional options for licensing and pricing your app.
\section{Creating Your Licensing and Pricing
Model}\label{creating-your-licensing-and-pricing-model}
Carefully consider which licensing structure best meets your needs. Once
your app has been approved, these options, except for price updates,
cannot be changed.
\textbf{Choose a license term:}
\begin{figure}
\centering
\includegraphics{./images/marketplace-configure-app-license.png}
\caption{Choosing license terms for Marketplace apps is easy.}
\end{figure}
Choosing \emph{Perpetual} allows the app to continue running without
expiration. Choosing \emph{Non-Perpetual} expires the app's license one
year from the purchase date. Perpetual License also allows you to offer
Support Services, which the customer must renew annually to maintain
access to app updates and support. If you choose not to offer Support
Services with a Perpetual License, customers will be provided with app
updates only, whenever updates are available.
Importantly, you can't change your app's license terms (perpetual or
non-perpetual) once the app is approved. In order to release an approved
app under a different license term you must submit another app under a
new name (title). So make sure you think through the license term that
makes the most sense for your app.
\textbf{Creating license options:}
\begin{figure}
\centering
\includegraphics{./images/marketplace-create-license-types.png}
\caption{You can create multiple license options for your Marketplace
apps.}
\end{figure}
Creating license options allows you to design license bundles and to
specify discounts for customers who purchase more Liferay Instances for
your app (a Liferay Instance or Instance refers to a single installation
of the Liferay Portal). Also you can designate different pricing for
Standard Licenses vs. Developer Licenses. You must specify at least one
license option, but no more than 10 options per type. You'll price these
options on the next page.
You can add or remove bundles (quantities) of your app's license type
even after the app is approved. You must however, always honor any
support agreements you have with current customers that are using
bundles you've removed.
\textbf{Paid support:} You can offer additional paid support services
for your app. If you select this option, customers can contact you with
support requests and are entitled to regular updates.
Once an app that offers a support subscription offering is approved, you
can't remove that support subscription offering. You can however, add a
support subscription offering to an approved app.
\textbf{Offer a trial:} You can offer a free 30-day trial of your app,
restricted to one Instance and 25 users.
When you're finished selecting all the options for your license, proceed
to the next page to determine the app's pricing and availability.
\textbf{Pricing:}
\begin{figure}
\centering
\includegraphics{./images/marketplace-app-pricing.png}
\caption{Liferay makes it easy to price your app's license types and
specify their availability to countries around the world.}
\end{figure}
Based on your selections from the previous page, you'll have price
fields for each license option and for any support option you offered.
When you have completed your app's pricing and availability, click
\emph{Next} to advance to the app preview screen.
\section{Submitting Your App For
Review}\label{submitting-your-app-for-review}
The \emph{App Preview} screen lets you preview your app as it will
appear on the Marketplace. If you want to change things before finally
submitting your app, click \emph{Edit} to go back and continue making
changes until you're satisfied.
Before finalizing your app's submission, make sure that you're pleased
with the values you've set for the following app details, as these
details can't be modified once the app is approved:
\begin{itemize}
\tightlist
\item
Free vs.~paid
\item
License term (perpetual vs.~non-perpetual)
\item
Support subscription offering (cannot be removed)
\end{itemize}
Once you are satisfied, click \emph{Submit for Review}.
At this point, the Liferay Marketplace staff starts to review your app
and test it. If you need to make changes to your app during its review
or if you're curious about the rigors of the review process, make sure
to read the next section of this guide.
\chapter{Understanding the App Review
Process}\label{understanding-the-app-review-process}
The Liferay Marketplace app QA/review process begins as soon as you
submit your app for review. Every third-party app submitted to the
Liferay Marketplace is reviewed by our team to ensure that certain
standards for information are upheld and the app installs as expected.
\emph{Liferay cannot, however, be a substitute for your own testing and
debugging team}. Ultimately, you must test, refine, and ensure that your
app functions as promised and performs as expected.
\noindent\hrulefill
\textbf{Note:} Liferay is not responsible for the behavior (or
misbehavior) of apps on the Marketplace. For details regarding this,
consult the \emph{Liferay Marketplace User Agreement}, \emph{Liferay
Marketplace Developer Agreement}, and the individual \emph{End User
License Agreements} associated with each app.
\noindent\hrulefill
Once you've submitted your app for review, your app's status changes as
it moves through the review process. We email you on status changes and
provide as much detail as we can if we discover potential issues with
your app. Overall, we don't want our app review process to feel like a
barrier or a black box. We love having new apps in the Marketplace, and
we try to be as helpful as we can throughout the approval process!
\begin{figure}
\centering
\includegraphics{./images/app_review_process.png}
\caption{Liferay informs you at every step during the QA/review
process.}
\end{figure}
If you submit an updated version of a previously approved app, you'll
see these statuses: Approved (Version Unsubmitted), Approved (Version
Pending), Approved (Version Pending QA), and Approved (Version Denied).
The app review process consists of \textbf{Two Major Phases}:
\phantomsection\label{article-33460874}
Review Phase
Est. Time Frame
App metadata review
Our team reviews your app's metadata to confirm that titles,
descriptions, images, etc. are appropriate.
\textasciitilde1 week
App QA test
Liferay ensures that apps meet a minimal set of requirements:
Passes anti-virus scan
Deploys successfully on standard Liferay supported
environments/platforms without errors.
Basic functionality ``smoke'' test.
Liferay does not do source code review and does not ask for your source
code. Further, Liferay is not responsible for the behavior (or
misbehavior) of apps on the Marketplace. Please consult the Liferay
Marketplace User Agreement, Liferay Marketplace Developer Agreement, and
the individual End User License Agreements associated with each app.
\textasciitilde1-2 weeks
\textbf{Our QA Test Environments} are summarized below. At a minimum,
test your app against these environments prior to submission. If
technical reasons prevent your app from running on certain platforms
(e.g., app server-specific issues), specify compatibility in the app
description and documentation so that our review team can exclude
certain test conditions, if necessary.
\phantomsection\label{article-33460919}
Liferay Version
6.x
7.0.x
7.1.x
7.2.x
7.3.x
Operating Systems
Ubuntu 11x and Windows 10 x64
Database
MySQL 5.5.x
MySQL 5.6.x
MySQL 5.7.x
Application Server *
Tomcat 7
Tomcat 8
Tomcat 9
JDK
Oracle JDK 6, 7
Oracle JDK 8 / JDK 11 (7.1 and higher)
Browser
Chrome
\textbf{*} You can request certification on additional, optional
application servers. For Liferay 7.x, Wildfly 10 is available. For
Liferay 6.x, Glassfish 3.1 and an appropriate JBoss version are
available. Please add the request in the app submission panel's
\emph{Note to testers} section.
Once your app is approved by Marketplace staff, you'll get email
notification. When your app is approved, it is made available on
Marketplace. The app also appears on your public Profile page, which
lists all apps that you or your company developed and published.
If your app is rejected, you receive an email explaining the reasons for
rejection. You can then make the requested changes and re-submit the app
for approval.
After you've successfully published your app, you might get all kinds of
feedback from users and yourself about what's right and wrong with it.
The next article explores how to make changes once you have published
your app.
\chapter{Managing Your Published App}\label{managing-your-published-app}
You've launched your new app on the Marketplace. Congratulations! As you
settle down from your launch celebration, you might wonder how your app
is performing in the Marketplace. Is there a way to monitor how many
people are viewing it and downloading it? And what about those features
that didn't make this release? How difficult is it to upload your app's
next version with those features? Don't sweat it. The Liferay
Marketplace and app wizard are here to help.
You can track app performance. Marketplace shows app trends like views,
downloads, and purchases. You can upload new versions of your app and
make changes to some of its details with the App Wizard. Read on to
learn how all of this works.
\section{Tracking App Performance}\label{tracking-app-performance}
One of the main reasons for developing and publishing apps on
Marketplace is to drive app downloads and adoption. Marketplace gives
you detailed reports on views, downloads, and purchases of your app(s).
To access these metrics, navigate to \emph{Account Home} →
\emph{Metrics} (under \emph{Development}).
\begin{figure}
\centering
\includegraphics{./images/marketplace-app-metrics-over-time.png}
\caption{The App Performance view in Marketplace lets you see how many
times your apps have been viewed, downloaded, and purchased over a time
interval.}
\end{figure}
The view shown above is the default metrics view for a developer's apps.
Across the bottom is a list of data series options (\emph{Views},
\emph{Downloads}, or \emph{Purchases}). At the top, you set a date
range. In the middle, a graph shows the data within the date range.
Finally, the graphed data is also shown in tabular format, in case you
want to know the exact values making up the graph. The different types
of data available to view are described below.
\subsection{Views}\label{views}
When someone searches or browses the Marketplace, they click on apps to
see their details. When this occurs for an app, Marketplace records a
\emph{View} for it. When you select \emph{Views} in the App Metrics
screen, the app view count over time appears. The App Metrics screen
shows \emph{Views} by default. The number of possible views per day per
user is unlimited.
\subsection{Downloads}\label{downloads}
Marketplace records a \emph{Download} for your app when someone
downloads any package of any version of your app. The number of possible
downloads per day per user is unlimited.
\subsection{Purchases}\label{purchases}
The Marketplace counts app purchases too.
As you see areas to improve your app or when necessary changes come up,
you can make changes to your published app. The next article describes
changes you can make.
\section{Making Changes to Published
Apps}\label{making-changes-to-published-apps}
You can make these kinds of changes during the life of your published
app:
\begin{itemize}
\tightlist
\item
Edit your app details (e.g., description, icon, etc.)
\item
Add new license bundles (quantities)
\item
Edit app prices of bundles
\item
Modify regional availability
\item
Add support for a new version of Liferay
\item
Release a new version of your app to fix bugs or offer new
functionality
\item
Disable your app
\end{itemize}
Your currently approved app remains available while you're submitting
changes and while Liferay reviews and tests your app changes.
The rest of this article describes each kind of change.
\subsection{Editing Your App's
Metadata}\label{editing-your-apps-metadata}
App metadata includes the name, description, icon, screenshots, and
other information you supplied during the app creation process. Here's
how to edit your app's metadata:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Account Home} → \emph{Development} → \emph{Apps}. A
listing of your published apps appears.
\item
Select the app you wish to edit. This screen shows you what the app
looks like on Marketplace.
\item
Click the \emph{Edit} button at the bottom of the preview. Now you can
edit details and add new files to the your existing version of your
app. The current values as they appear in your app pre-populate the
form.
\item
Make any changes as needed on this screen.
\item
Click \emph{Next}. If you don't need to edit any more variations, you
can continue clicking \emph{Next} until you reach the final preview
screen.
\item
Click \emph{Submit for Review} to submit your changes for review.
\end{enumerate}
All changes must go through the submission process. If your app revision
only consists of metadata changes, it need only go through the
\href{/how-to-publish/-/knowledge_base/publish/understanding-the-app-review-process}{\emph{App
metadata review}} phase of the review process.
Once approved, the changes you request appear for your app on the
Marketplace.
\subsection{Editing App Prices}\label{editing-app-prices}
You can change your app's prices, add or remove bundles, and modify
regional availability for a variety of reasons, whether it's to run a
promotional offer, or to adjust your pricing model to better account for
app demand. Here's how to edit app prices:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Company Profile Home} → \emph{Apps}. A listing of
your published apps appears.
\item
Select the app you wish to edit.
\item
At the bottom, click \emph{Edit} → \emph{Pricing}.
\item
Edit your app's prices.
\item
When you've finished editing your app's prices, click \emph{Next} and
save your app.
\end{enumerate}
These changes do not require Liferay verification process to approve.
The changes show immediately.
You cannot however, modify the following attributes of an approved app:
\begin{itemize}
\tightlist
\item
Free vs.~paid
\item
License term (perpetual vs.~non-perpetual)
\item
Support subscription offering (cannot be removed)
\end{itemize}
Next, you can consider adding support for new versions of Liferay.
\subsection{Adding Support for New Versions of
Liferay}\label{adding-support-for-new-versions-of-liferay}
If you must add files to support another Liferay release, the process is
similar to editing an app.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Account Home} → \emph{Apps}. A listing of your
published apps appears.
\item
Select the app you wish to edit.
\item
Click the \emph{Edit} button to edit that app.
\item
Click \emph{Next} to advance past the details screen (make any
necessary changes). The version edit screen appears.
\item
Click \emph{Next} to advance past the version edit screen (you can't
actually edit the version number of an already-approved version, but
you can edit the ``What's New'' information). The File Upload screen
appears.
This screen should look familiar---it's the same workflow used when
you initially created your app! The difference is that you can't edit
pre-approved files for specific Liferay releases. You can only add
\emph{new} files for a different Liferay release (if you must update
existing files, you must create a new version of the app---see the
later section on adding versions for details on how to do this).
\item
Upload your new files (ensuring that your new plugins have updated
compatibility information: see the
\href{/how-to-publish/-/knowledge_base/publish/preparing-your-app}{Marketplace
App Metadata Guidelines} for details on versions).
\item
Click \emph{Next}, and observe the newly-added files listed at the
bottom of the preview screen.
\item
Click \emph{Submit for Review} to submit your requested change (adding
files).
\end{enumerate}
Liferay reviews the files, and once approved, the new package becomes
available for download in the Marketplace.
\subsection{Releasing a New Version of your
App}\label{releasing-a-new-version-of-your-app}
As time passes, you may wish to add new functionality to your app or fix
a batch of bugs. This can be accomplished by releasing a new version of
your app. New versions offer your users new functionality and bug fixes,
and users are generally encouraged to use the latest version. In
addition, when a new version of your app becomes available, Marketplace
notifies existing users.
New versions of your apps are created in a similar fashion to creating
new apps.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Account Home} → \emph{Apps}. A listing of your
published apps appears.
\item
Select the app you wish to edit.
\item
Click the \emph{Edit} button to edit that app. The Details screen
appears.
\item
At the bottom of the Details screen, click the \emph{Add New Version}
button. The process of adding a new version begins, starting with the
App Details screen. Data from the current version of the app
pre-populates the screen.
You can modify the screen's pre-populated data. Since this is a new
version of an existing app, making major changes (such as completely
changing the name or description) might unsettle existing users.
Uploading new screenshots and refreshing app icons is common. Note
that you cannot change the app owner (such as moving from a
personally-developed app to a company-developed app).
\item
Click \emph{Next} until you get to the \emph{Add App Version} screen.
\item
Specify a new version name for this version of your app.
\item
Add \emph{What's New} text (optional). Common changes to mention are
new features and bug fix information.
\item
Click \emph{Next}. The file upload screen appears.
\item
Upload the files associated with the new version of the app. You must
upload all files for all supported Liferay versions again, even if
they have not changed since the last version of your app.
\item
Continue clicking \emph{Next} until you reach the final preview
screen.
\item
Click \emph{Submit for Review} to submit the new version of your app
for review.
\end{enumerate}
The Liferay Marketplace staff starts to review your app's new version
and test it.
\subsection{Deactivating Your App}\label{deactivating-your-app}
When the time comes to retire your app, you can \emph{Deactivate} it.
Deactivating an app makes it unavailable to new customers, and it
doesn't appear in any public Marketplace listings. Existing customers
that have already downloaded your app can continue downloading the
legacy versions of the app they have already acquired, but they can't
download any versions they've not already received. The app remains in
your inventory, with all of its history, in case you choose to
re-activate or reference it.
Here's how to deactivate your app:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Account Home} → \emph{Apps}. A listing of your
published apps appears.
\item
Select the app you wish to deactivate.
\item
Click the \emph{Deactivate} button.
\end{enumerate}
In this set of articles, we looked at how to create, publish, maintain,
and track Liferay Marketplace apps. You can do this through the App
Manager that's available on your \emph{Account Home} page on
\href{http://liferay.com}{liferay.com} (liferay.com) account required!).
We covered requirements for publishing variations of your apps for
different versions of Liferay. Next, we showed how you can publish an
app on the Marketplace and how you can modify the app as it evolves.
Finally, we looked at tracking app adoption using view, download, and
install metrics.
================================================
FILE: book/developer/reference.aux
================================================
\relax
\providecommand{\transparent@use}[1]{}
\providecommand\hyper@newdestlabel[2]{}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {640}Developer Reference}{1873}{chapter.640}\protected@file@percent }
\newlabel{developer-reference}{{640}{1873}{Developer Reference}{chapter.640}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {641}Classes Moved From portal-service.jar}{1875}{chapter.641}\protected@file@percent }
\newlabel{classes-moved-from-portal-service.jar}{{641}{1875}{Classes Moved From portal-service.jar}{chapter.641}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {642}Export/Import and Staging}{1929}{chapter.642}\protected@file@percent }
\newlabel{exportimport-and-staging}{{642}{1929}{Export/Import and Staging}{chapter.642}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {643}Decision to Implement Staging}{1931}{chapter.643}\protected@file@percent }
\newlabel{decision-to-implement-staging}{{643}{1931}{Decision to Implement Staging}{chapter.643}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {644}Liferay Archive (LAR) File}{1933}{chapter.644}\protected@file@percent }
\newlabel{liferay-archive-lar-file}{{644}{1933}{Liferay Archive (LAR) File}{chapter.644}{}}
\@writefile{toc}{\contentsline {section}{\numberline {644.1}LAR File Anatomy}{1933}{section.644.1}\protected@file@percent }
\newlabel{lar-file-anatomy}{{644.1}{1933}{LAR File Anatomy}{section.644.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {644.2}LAR Manifest}{1934}{section.644.2}\protected@file@percent }
\newlabel{lar-manifest}{{644.2}{1934}{LAR Manifest}{section.644.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {644.3}LAR Folders}{1935}{section.644.3}\protected@file@percent }
\newlabel{lar-folders}{{644.3}{1935}{LAR Folders}{section.644.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {644.1}{\ignorespaces Entities, Portlets, and Pages are defined in a LAR in different places.}}{1936}{figure.644.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {645}Front-End Reference}{1937}{chapter.645}\protected@file@percent }
\newlabel{front-end-reference}{{645}{1937}{Front-End Reference}{chapter.645}{}}
\gdef \LT@xxxii {\LT@entry
{1}{114.43875pt}\LT@entry
{1}{120.43875pt}\LT@entry
{1}{120.43875pt}\LT@entry
{1}{114.43875pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {646}Liferay DXP FreeMarker Macros}{1939}{chapter.646}\protected@file@percent }
\newlabel{liferay-dxp-freemarker-macros}{{646}{1939}{Liferay DXP FreeMarker Macros}{chapter.646}{}}
\@writefile{toc}{\contentsline {section}{\numberline {646.1}Reference Examples}{1940}{section.646.1}\protected@file@percent }
\newlabel{reference-examples}{{646.1}{1940}{Reference Examples}{section.646.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {647}Front-End Taglibs}{1943}{chapter.647}\protected@file@percent }
\newlabel{front-end-taglibs}{{647}{1943}{Front-End Taglibs}{chapter.647}{}}
\gdef \LT@xxxiii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {648}Liferay Theme Objects Available in JSPs}{1945}{chapter.648}\protected@file@percent }
\newlabel{liferay-theme-objects-available-in-jsps}{{648}{1945}{Liferay Theme Objects Available in JSPs}{chapter.648}{}}
\gdef \LT@xxxiv {\LT@entry
{1}{167.54416pt}\LT@entry
{1}{302.21085pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {649}Liferay Portlet Objects Available in JSPs}{1947}{chapter.649}\protected@file@percent }
\newlabel{liferay-portlet-objects-available-in-jsps}{{649}{1947}{Liferay Portlet Objects Available in JSPs}{chapter.649}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {650}Using the Liferay UI Taglib}{1949}{chapter.650}\protected@file@percent }
\newlabel{using-the-liferay-ui-taglib}{{650}{1949}{Using the Liferay UI Taglib}{chapter.650}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {651}Liferay UI Icons}{1951}{chapter.651}\protected@file@percent }
\newlabel{liferay-ui-icons}{{651}{1951}{Liferay UI Icons}{chapter.651}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {651.1}{\ignorespaces Use the image attribute to use a theme icon.}}{1951}{figure.651.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {651.2}{\ignorespaces The Liferay UI taglib offers multiple icons for use in your app.}}{1952}{figure.651.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {651.3}{\ignorespaces Liferay UI icons can be configured based on language.}}{1953}{figure.651.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {651.4}{\ignorespaces You can use the icon attribute to include Font Awesome icons in your app.}}{1953}{figure.651.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {651.5}{\ignorespaces You can use Font Awesome icons in your app.}}{1953}{figure.651.5}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {651.1}Related Topics}{1953}{section.651.1}\protected@file@percent }
\newlabel{related-topics}{{651.1}{1953}{Related Topics}{section.651.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {652}Liferay UI Icon Lists}{1955}{chapter.652}\protected@file@percent }
\newlabel{liferay-ui-icon-lists}{{652}{1955}{Liferay UI Icon Lists}{chapter.652}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {652.1}{\ignorespaces Icon lists display an app's actions at all times.}}{1955}{figure.652.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {652.1}Related Topics}{1956}{section.652.1}\protected@file@percent }
\newlabel{related-topics-1}{{652.1}{1956}{Related Topics}{section.652.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {653}Liferay UI Icon Menus}{1957}{chapter.653}\protected@file@percent }
\newlabel{liferay-ui-icon-menus}{{653}{1957}{Liferay UI Icon Menus}{chapter.653}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {653.1}{\ignorespaces Setting up an icon menu is a piece of cake.}}{1957}{figure.653.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {653.1}Related Topics}{1958}{section.653.1}\protected@file@percent }
\newlabel{related-topics-2}{{653.1}{1958}{Related Topics}{section.653.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {654}Liferay UI Tabs}{1959}{chapter.654}\protected@file@percent }
\newlabel{liferay-ui-tabs}{{654}{1959}{Liferay UI Tabs}{chapter.654}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {654.1}{\ignorespaces Tabs are a useful way to organize configuration options into individual sections within the same UI.}}{1960}{figure.654.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {654.1}Related Topics}{1960}{section.654.1}\protected@file@percent }
\newlabel{related-topics-3}{{654.1}{1960}{Related Topics}{section.654.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {655}Liferay UI Icon Help}{1961}{chapter.655}\protected@file@percent }
\newlabel{liferay-ui-icon-help}{{655}{1961}{Liferay UI Icon Help}{chapter.655}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {655.1}{\ignorespaces Here's an example of the icon help tag.}}{1961}{figure.655.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {655.1}Related Topics}{1962}{section.655.1}\protected@file@percent }
\newlabel{related-topics-4}{{655.1}{1962}{Related Topics}{section.655.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {655.2}{\ignorespaces help icons are used throughout the Control Panel.}}{1963}{figure.655.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {656}Using Liferay Front-end Taglibs in Your Portlet}{1965}{chapter.656}\protected@file@percent }
\newlabel{using-liferay-front-end-taglibs-in-your-portlet}{{656}{1965}{Using Liferay Front-end Taglibs in Your Portlet}{chapter.656}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {657}Liferay Front-end Add Menu}{1967}{chapter.657}\protected@file@percent }
\newlabel{liferay-front-end-add-menu}{{657}{1967}{Liferay Front-end Add Menu}{chapter.657}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {657.1}{\ignorespaces The add button pattern consists of an \texttt {add-menu} tag and at least one \texttt {add-menu-item} tag.}}{1967}{figure.657.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {657.2}{\ignorespaces The add button pattern consists of an \texttt {add-menu} tag and at least one \texttt {add-menu-item} tag.}}{1968}{figure.657.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {657.1}Related Topics}{1968}{section.657.1}\protected@file@percent }
\newlabel{related-topics-5}{{657.1}{1968}{Related Topics}{section.657.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {658}Liferay Front-end Cards}{1969}{chapter.658}\protected@file@percent }
\newlabel{liferay-front-end-cards}{{658}{1969}{Liferay Front-end Cards}{chapter.658}{}}
\@writefile{toc}{\contentsline {section}{\numberline {658.1}Horizontal Card}{1969}{section.658.1}\protected@file@percent }
\newlabel{horizontal-card}{{658.1}{1969}{Horizontal Card}{section.658.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {658.2}Icon Vertical Card}{1969}{section.658.2}\protected@file@percent }
\newlabel{icon-vertical-card}{{658.2}{1969}{Icon Vertical Card}{section.658.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {658.1}{\ignorespaces Horizontal cards are perfect to display files and documents.}}{1970}{figure.658.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {658.3}Vertical Card}{1970}{section.658.3}\protected@file@percent }
\newlabel{vertical-card}{{658.3}{1970}{Vertical Card}{section.658.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {658.2}{\ignorespaces Vertical icon cards are perfect to display an entity selection, such as a web content article.}}{1971}{figure.658.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {658.4}HTML Vertical Card}{1971}{section.658.4}\protected@file@percent }
\newlabel{html-vertical-card}{{658.4}{1971}{HTML Vertical Card}{section.658.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {658.3}{\ignorespaces Vertical cards are perfect to display files and documents.}}{1972}{figure.658.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {658.4}{\ignorespaces Html vertical cards let you display custom HTML in the card's header.}}{1973}{figure.658.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {658.5}User Vertical Card}{1973}{section.658.5}\protected@file@percent }
\newlabel{user-vertical-card}{{658.5}{1973}{User Vertical Card}{section.658.5}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {658.5}{\ignorespaces User vertical cards are perfect to display files and documents.}}{1974}{figure.658.5}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {658.6}Related Topics}{1974}{section.658.6}\protected@file@percent }
\newlabel{related-topics-6}{{658.6}{1974}{Related Topics}{section.658.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {659}Liferay Front-end Info Bar}{1977}{chapter.659}\protected@file@percent }
\newlabel{liferay-front-end-info-bar}{{659}{1977}{Liferay Front-end Info Bar}{chapter.659}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {659.1}{\ignorespaces The info bar tags create a sidebar panel toggler that reveals additional info.}}{1978}{figure.659.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {659.2}{\ignorespaces The info bar tags create a sidebar panel toggler that reveals additional info.}}{1979}{figure.659.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {659.1}Related Topics}{1979}{section.659.1}\protected@file@percent }
\newlabel{related-topics-7}{{659.1}{1979}{Related Topics}{section.659.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {660}Liferay Front-end Management Bar}{1981}{chapter.660}\protected@file@percent }
\newlabel{liferay-front-end-management-bar}{{660}{1981}{Liferay Front-end Management Bar}{chapter.660}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {660.1}{\ignorespaces The Management Bar lets the user customize how the app displays content.}}{1981}{figure.660.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {660.2}{\ignorespaces The \texttt {management-bar-buttons} tag contains the Management Bar's main buttons.}}{1982}{figure.660.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {660.3}{\ignorespaces The \texttt {management-bar-display-buttons} tag contains the content's display options.}}{1982}{figure.660.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {660.4}{\ignorespaces The \texttt {management-bar-filters} tag contains the content filtering options.}}{1982}{figure.660.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {660.5}{\ignorespaces The management bar keeps track of the items selected and displays the actions to execute on them.}}{1982}{figure.660.5}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {661}Including Actions in the Management Bar}{1985}{chapter.661}\protected@file@percent }
\newlabel{including-actions-in-the-management-bar}{{661}{1985}{Including Actions in the Management Bar}{chapter.661}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {661.1}{\ignorespaces You can select individual results or all results at once.}}{1985}{figure.661.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {661.2}{\ignorespaces You can have as many actions as your app requires.}}{1986}{figure.661.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {661.1}Related Topics}{1986}{section.661.1}\protected@file@percent }
\newlabel{related-topics-8}{{661.1}{1986}{Related Topics}{section.661.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {662}Disabling All or Portions of the Management Bar}{1987}{chapter.662}\protected@file@percent }
\newlabel{disabling-all-or-portions-of-the-management-bar}{{662}{1987}{Disabling All or Portions of the Management Bar}{chapter.662}{}}
\@writefile{toc}{\contentsline {section}{\numberline {662.1}Related Topics}{1987}{section.662.1}\protected@file@percent }
\newlabel{related-topics-9}{{662.1}{1987}{Related Topics}{section.662.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {662.1}{\ignorespaces You can disable all or portions of the Management Bar.}}{1988}{figure.662.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {663}Using the Liferay Util Taglib}{1989}{chapter.663}\protected@file@percent }
\newlabel{using-the-liferay-util-taglib}{{663}{1989}{Using the Liferay Util Taglib}{chapter.663}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {664}Using Liferay Util Body Bottom}{1991}{chapter.664}\protected@file@percent }
\newlabel{using-liferay-util-body-bottom}{{664}{1991}{Using Liferay Util Body Bottom}{chapter.664}{}}
\@writefile{toc}{\contentsline {section}{\numberline {664.1}Related Topics}{1991}{section.664.1}\protected@file@percent }
\newlabel{related-topics-10}{{664.1}{1991}{Related Topics}{section.664.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {665}Using Liferay Util Body Top}{1993}{chapter.665}\protected@file@percent }
\newlabel{using-liferay-util-body-top}{{665}{1993}{Using Liferay Util Body Top}{chapter.665}{}}
\@writefile{toc}{\contentsline {section}{\numberline {665.1}Related Topics}{1993}{section.665.1}\protected@file@percent }
\newlabel{related-topics-11}{{665.1}{1993}{Related Topics}{section.665.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {666}Using Liferay Util Buffer}{1995}{chapter.666}\protected@file@percent }
\newlabel{using-liferay-util-buffer}{{666}{1995}{Using Liferay Util Buffer}{chapter.666}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {666.1}{\ignorespaces You can use the Liferay Util Buffer tag to save pieces of markup to reuse in your JSP.}}{1995}{figure.666.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {666.1}Related Topics}{1995}{section.666.1}\protected@file@percent }
\newlabel{related-topics-12}{{666.1}{1995}{Related Topics}{section.666.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {667}Using Liferay Util Dynamic Include}{1997}{chapter.667}\protected@file@percent }
\newlabel{using-liferay-util-dynamic-include}{{667}{1997}{Using Liferay Util Dynamic Include}{chapter.667}{}}
\@writefile{toc}{\contentsline {section}{\numberline {667.1}Related Topics}{1997}{section.667.1}\protected@file@percent }
\newlabel{related-topics-13}{{667.1}{1997}{Related Topics}{section.667.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {668}Using Liferay Util Get URL}{1999}{chapter.668}\protected@file@percent }
\newlabel{using-liferay-util-get-url}{{668}{1999}{Using Liferay Util Get URL}{chapter.668}{}}
\@writefile{toc}{\contentsline {section}{\numberline {668.1}Related Topics}{1999}{section.668.1}\protected@file@percent }
\newlabel{related-topics-14}{{668.1}{1999}{Related Topics}{section.668.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {668.1}{\ignorespaces You can use the Liferay Util Get URL tag to scrape URLs.}}{2000}{figure.668.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {669}Using Liferay Util HTML Bottom}{2001}{chapter.669}\protected@file@percent }
\newlabel{using-liferay-util-html-bottom}{{669}{2001}{Using Liferay Util HTML Bottom}{chapter.669}{}}
\@writefile{toc}{\contentsline {section}{\numberline {669.1}Related Topics}{2001}{section.669.1}\protected@file@percent }
\newlabel{related-topics-15}{{669.1}{2001}{Related Topics}{section.669.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {670}Using Liferay Util HTML Top}{2003}{chapter.670}\protected@file@percent }
\newlabel{using-liferay-util-html-top}{{670}{2003}{Using Liferay Util HTML Top}{chapter.670}{}}
\@writefile{toc}{\contentsline {section}{\numberline {670.1}Related Topics}{2003}{section.670.1}\protected@file@percent }
\newlabel{related-topics-16}{{670.1}{2003}{Related Topics}{section.670.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {671}Using Liferay Util Include}{2005}{chapter.671}\protected@file@percent }
\newlabel{using-liferay-util-include}{{671}{2005}{Using Liferay Util Include}{chapter.671}{}}
\@writefile{toc}{\contentsline {section}{\numberline {671.1}Related Topics}{2005}{section.671.1}\protected@file@percent }
\newlabel{related-topics-17}{{671.1}{2005}{Related Topics}{section.671.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {672}Using Liferay Util Param}{2007}{chapter.672}\protected@file@percent }
\newlabel{using-liferay-util-param}{{672}{2007}{Using Liferay Util Param}{chapter.672}{}}
\@writefile{toc}{\contentsline {section}{\numberline {672.1}Related Topics}{2007}{section.672.1}\protected@file@percent }
\newlabel{related-topics-18}{{672.1}{2007}{Related Topics}{section.672.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {673}Using Liferay Util Whitespace Remover}{2009}{chapter.673}\protected@file@percent }
\newlabel{using-liferay-util-whitespace-remover}{{673}{2009}{Using Liferay Util Whitespace Remover}{chapter.673}{}}
\@writefile{toc}{\contentsline {section}{\numberline {673.1}Related Topics}{2009}{section.673.1}\protected@file@percent }
\newlabel{related-topics-19}{{673.1}{2009}{Related Topics}{section.673.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {674}Using the Clay Taglib in Your portlets}{2011}{chapter.674}\protected@file@percent }
\newlabel{using-the-clay-taglib-in-your-portlets}{{674}{2011}{Using the Clay Taglib in Your portlets}{chapter.674}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {675}Clay Alerts}{2013}{chapter.675}\protected@file@percent }
\newlabel{clay-alerts}{{675}{2013}{Clay Alerts}{chapter.675}{}}
\@writefile{toc}{\contentsline {section}{\numberline {675.1}Embedded Alerts}{2013}{section.675.1}\protected@file@percent }
\newlabel{embedded-alerts}{{675.1}{2013}{Embedded Alerts}{section.675.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {675.1}{\ignorespaces The danger alert notifies the user of an error or issue.}}{2013}{figure.675.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {675.2}{\ignorespaces The success alert notifies the user when an action is successful.}}{2014}{figure.675.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {675.3}{\ignorespaces The info alert displays general information to the user.}}{2014}{figure.675.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {675.4}{\ignorespaces The warning alert displays a warning message to the user.}}{2014}{figure.675.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {675.2}Stripe Alerts}{2014}{section.675.2}\protected@file@percent }
\newlabel{stripe-alerts}{{675.2}{2014}{Stripe Alerts}{section.675.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {675.5}{\ignorespaces The danger striped alert notifies the user that an action has failed.}}{2014}{figure.675.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {675.6}{\ignorespaces The success striped alert notifies the user that an action has completed successfully.}}{2015}{figure.675.6}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {675.7}{\ignorespaces The info striped alert displays general information about an action to the user.}}{2015}{figure.675.7}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {675.8}{\ignorespaces The warning striped alert warns the user about an action.}}{2015}{figure.675.8}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {675.3}Related Topics}{2015}{section.675.3}\protected@file@percent }
\newlabel{related-topics-20}{{675.3}{2015}{Related Topics}{section.675.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {676}Clay Badges}{2017}{chapter.676}\protected@file@percent }
\newlabel{clay-badges}{{676}{2017}{Clay Badges}{chapter.676}{}}
\@writefile{toc}{\contentsline {section}{\numberline {676.1}Badge Types}{2017}{section.676.1}\protected@file@percent }
\newlabel{badge-types}{{676.1}{2017}{Badge Types}{section.676.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {676.1}{\ignorespaces A primary badge is bright blue, commanding attention like the primary button of a form.}}{2017}{figure.676.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {676.2}{\ignorespaces A secondary badge is light-grey and draws less focus than a primary button.}}{2018}{figure.676.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {676.3}{\ignorespaces A info badge is dark blue and meant for numbers related to general information.}}{2018}{figure.676.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {676.4}{\ignorespaces An error badge displays numbers related to an error.}}{2019}{figure.676.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {676.5}{\ignorespaces A success badge displays numbers related to a successful action.}}{2019}{figure.676.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {676.6}{\ignorespaces A warning badge displays numbers related to warnings that should be addressed.}}{2019}{figure.676.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {676.2}Related Topics}{2020}{section.676.2}\protected@file@percent }
\newlabel{related-topics-21}{{676.2}{2020}{Related Topics}{section.676.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {677}Clay Buttons}{2021}{chapter.677}\protected@file@percent }
\newlabel{clay-buttons}{{677}{2021}{Clay Buttons}{chapter.677}{}}
\@writefile{toc}{\contentsline {section}{\numberline {677.1}Types}{2021}{section.677.1}\protected@file@percent }
\newlabel{types}{{677.1}{2021}{Types}{section.677.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {677.1}{\ignorespaces A primary button is bright blue, grabbing the user's attention.}}{2021}{figure.677.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.2}{\ignorespaces A secondary button draws less attention than a primary button and is meant for secondary actions.}}{2022}{figure.677.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.3}{\ignorespaces Borderless buttons remove the dark outline from the button.}}{2022}{figure.677.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.4}{\ignorespaces You can also turn buttons into links.}}{2022}{figure.677.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.5}{\ignorespaces Buttons can also display icons.}}{2022}{figure.677.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.6}{\ignorespaces Buttons can be disabled if you don't want the user to interact with them.}}{2023}{figure.677.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {677.2}Variations}{2023}{section.677.2}\protected@file@percent }
\newlabel{variations}{{677.2}{2023}{Variations}{section.677.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {677.7}{\ignorespaces Buttons can display both icons and text.}}{2023}{figure.677.7}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.8}{\ignorespaces Buttons can display monospaced text.}}{2023}{figure.677.8}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.9}{\ignorespaces Block level buttons span the entire width of the container.}}{2024}{figure.677.9}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.10}{\ignorespaces : A plus button is used for add actions in an app.}}{2024}{figure.677.10}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {677.11}{\ignorespaces : An action button is used to display actions menus.}}{2024}{figure.677.11}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {677.3}Related Topics}{2024}{section.677.3}\protected@file@percent }
\newlabel{related-topics-22}{{677.3}{2024}{Related Topics}{section.677.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {678}Clay Cards}{2025}{chapter.678}\protected@file@percent }
\newlabel{clay-cards}{{678}{2025}{Clay Cards}{chapter.678}{}}
\@writefile{toc}{\contentsline {section}{\numberline {678.1}Image Cards}{2025}{section.678.1}\protected@file@percent }
\newlabel{image-cards}{{678.1}{2025}{Image Cards}{section.678.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {678.1}{\ignorespaces Image Cards display images and documents.}}{2026}{figure.678.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {678.2}{\ignorespaces Image Cards can also display icons instead of images.}}{2027}{figure.678.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {678.2}File Cards}{2027}{section.678.2}\protected@file@percent }
\newlabel{file-cards}{{678.2}{2027}{File Cards}{section.678.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {678.3}{\ignorespaces Cards can also display nothing.}}{2028}{figure.678.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {678.4}{\ignorespaces Cards can also contain file types.}}{2029}{figure.678.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {678.3}User Cards}{2029}{section.678.3}\protected@file@percent }
\newlabel{user-cards}{{678.3}{2029}{User Cards}{section.678.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {678.5}{\ignorespaces You can include labels in Cards.}}{2030}{figure.678.5}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {678.4}Horizontal Cards}{2030}{section.678.4}\protected@file@percent }
\newlabel{horizontal-cards}{{678.4}{2030}{Horizontal Cards}{section.678.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {678.5}Related Topics}{2030}{section.678.5}\protected@file@percent }
\newlabel{related-topics-23}{{678.5}{2030}{Related Topics}{section.678.5}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {678.6}{\ignorespaces Cards can be selectable.}}{2031}{figure.678.6}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {678.7}{\ignorespaces File Cards display file type icons.}}{2032}{figure.678.7}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {678.8}{\ignorespaces User Cards can display a user's initials.}}{2033}{figure.678.8}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {678.9}{\ignorespaces A User Card can also display a profile image.}}{2033}{figure.678.9}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {678.10}{\ignorespaces : Horizontal Cards are good for displaying folders.}}{2034}{figure.678.10}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {679}Clay Dropdown Menus and Action Menus}{2035}{chapter.679}\protected@file@percent }
\newlabel{clay-dropdown-menus-and-action-menus}{{679}{2035}{Clay Dropdown Menus and Action Menus}{chapter.679}{}}
\@writefile{toc}{\contentsline {section}{\numberline {679.1}Dropdown Menus}{2035}{section.679.1}\protected@file@percent }
\newlabel{dropdown-menus}{{679.1}{2035}{Dropdown Menus}{section.679.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {679.1}{\ignorespaces Clay taglibs provide everything you need to add dropdown menus to your app.}}{2036}{figure.679.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {679.2}{\ignorespaces You can organize dropdown menu items into groups.}}{2037}{figure.679.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {679.2}Actions Menus}{2037}{section.679.2}\protected@file@percent }
\newlabel{actions-menus}{{679.2}{2037}{Actions Menus}{section.679.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {679.3}{\ignorespaces Inputs can be included in dropdown menus.}}{2038}{figure.679.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {679.4}{\ignorespaces Icons can be included in dropdown menus.}}{2039}{figure.679.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {679.5}{\ignorespaces You can also create Actions menus with Clay taglibs.}}{2039}{figure.679.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {679.6}{\ignorespaces You can provide help text in Actions menus.}}{2040}{figure.679.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {679.3}Related Topics}{2041}{section.679.3}\protected@file@percent }
\newlabel{related-topics-24}{{679.3}{2041}{Related Topics}{section.679.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {680}Clay Form Elements}{2043}{chapter.680}\protected@file@percent }
\newlabel{clay-form-elements}{{680}{2043}{Clay Form Elements}{chapter.680}{}}
\@writefile{toc}{\contentsline {section}{\numberline {680.1}Checkbox}{2043}{section.680.1}\protected@file@percent }
\newlabel{checkbox}{{680.1}{2043}{Checkbox}{section.680.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {680.1}{\ignorespaces Clay taglibs provide checkboxes.}}{2043}{figure.680.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {680.2}Radio}{2043}{section.680.2}\protected@file@percent }
\newlabel{radio}{{680.2}{2043}{Radio}{section.680.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {680.2}{\ignorespaces Clay taglibs provide radio buttons.}}{2044}{figure.680.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {680.3}Selector}{2044}{section.680.3}\protected@file@percent }
\newlabel{selector}{{680.3}{2044}{Selector}{section.680.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {680.3}{\ignorespaces Clay taglibs provide select boxes.}}{2045}{figure.680.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {680.4}{\ignorespaces You can let users select multiple options from the select menu.}}{2046}{figure.680.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {680.4}Related Topics}{2046}{section.680.4}\protected@file@percent }
\newlabel{related-topics-25}{{680.4}{2046}{Related Topics}{section.680.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {681}Clay Icons}{2047}{chapter.681}\protected@file@percent }
\newlabel{clay-icons}{{681}{2047}{Clay Icons}{chapter.681}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {681.1}{\ignorespaces You can include icons in your app with the Clay taglib.}}{2047}{figure.681.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {681.1}Related Topics}{2047}{section.681.1}\protected@file@percent }
\newlabel{related-topics-26}{{681.1}{2047}{Related Topics}{section.681.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {681.2}{\ignorespaces The Clay taglib gives you access to several Liferay DXP icons.}}{2048}{figure.681.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {681.3}{\ignorespaces You can include language flags in your apps.}}{2048}{figure.681.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {682}Clay Labels and Links}{2049}{chapter.682}\protected@file@percent }
\newlabel{clay-labels-and-links}{{682}{2049}{Clay Labels and Links}{chapter.682}{}}
\@writefile{toc}{\contentsline {section}{\numberline {682.1}Labels}{2049}{section.682.1}\protected@file@percent }
\newlabel{labels}{{682.1}{2049}{Labels}{section.682.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {682.2}Color-coded Labels}{2049}{section.682.2}\protected@file@percent }
\newlabel{color-coded-labels}{{682.2}{2049}{Color-coded Labels}{section.682.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {682.1}{\ignorespaces Info labels convey general information.}}{2049}{figure.682.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {682.2}{\ignorespaces Status labels are the least flashy and best for displaying basic information.}}{2050}{figure.682.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {682.3}{\ignorespaces Warning labels notify the user of issues, but nothing app breaking.}}{2050}{figure.682.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {682.4}{\ignorespaces Danger labels convey a sense of urgency that must be addressed.}}{2050}{figure.682.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {682.5}{\ignorespaces Success labels indicate a successful action.}}{2050}{figure.682.5}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {682.3}Removable Labels}{2051}{section.682.3}\protected@file@percent }
\newlabel{removable-labels}{{682.3}{2051}{Removable Labels}{section.682.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {682.6}{\ignorespaces Labels can be removable.}}{2051}{figure.682.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {682.4}Labels with Links}{2051}{section.682.4}\protected@file@percent }
\newlabel{labels-with-links}{{682.4}{2051}{Labels with Links}{section.682.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {682.7}{\ignorespaces Labels can also be links.}}{2051}{figure.682.7}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {682.5}Links}{2051}{section.682.5}\protected@file@percent }
\newlabel{links}{{682.5}{2051}{Links}{section.682.5}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {682.8}{\ignorespaces Clay taglibs also provide link elements.}}{2051}{figure.682.8}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {682.6}Related Topics}{2052}{section.682.6}\protected@file@percent }
\newlabel{related-topics-27}{{682.6}{2052}{Related Topics}{section.682.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {683}Clay Management Toolbar}{2053}{chapter.683}\protected@file@percent }
\newlabel{clay-management-toolbar}{{683}{2053}{Clay Management Toolbar}{chapter.683}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {683.1}{\ignorespaces The Management ToolBar lets the user customize how the app displays content.}}{2053}{figure.683.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {683.1}Using a Display Context to Configure the Management Toolbar}{2053}{section.683.1}\protected@file@percent }
\newlabel{using-a-display-context-to-configure-the-management-toolbar}{{683.1}{2053}{Using a Display Context to Configure the Management Toolbar}{section.683.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {683.2}Checkbox and Actions}{2054}{section.683.2}\protected@file@percent }
\newlabel{checkbox-and-actions}{{683.2}{2054}{Checkbox and Actions}{section.683.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {683.2}{\ignorespaces Actions are also listed in the Management Toolbar's dropdown menu when an item, multiple items, or the master checkbox is checked.}}{2054}{figure.683.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {683.3}{\ignorespaces The Management Toolbar keeps track of the results selected and displays the actions to execute on them.}}{2055}{figure.683.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {683.3}Filtering and Sorting Search Results}{2055}{section.683.3}\protected@file@percent }
\newlabel{filtering-and-sorting-search-results}{{683.3}{2055}{Filtering and Sorting Search Results}{section.683.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {683.4}{\ignorespaces You can also sort and filter search container results.}}{2056}{figure.683.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {683.5}{\ignorespaces You can also sort and filter search container results.}}{2056}{figure.683.5}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {683.4}Search Form}{2057}{section.683.4}\protected@file@percent }
\newlabel{search-form}{{683.4}{2057}{Search Form}{section.683.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {683.6}{\ignorespaces The search form comprises most of the Management Toolbar, letting users search through the search container results.}}{2057}{figure.683.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {683.5}Info Panel}{2057}{section.683.5}\protected@file@percent }
\newlabel{info-panel}{{683.5}{2057}{Info Panel}{section.683.5}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {683.7}{\ignorespaces The info panel keeps your UI clutter-free.}}{2058}{figure.683.7}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {683.6}View Types}{2058}{section.683.6}\protected@file@percent }
\newlabel{view-types}{{683.6}{2058}{View Types}{section.683.6}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {683.8}{\ignorespaces The Management Toolbar's icon display view gives a quick summary of the content's description and status.}}{2059}{figure.683.8}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {683.9}{\ignorespaces The Management Toolbar's List view type gives the content's full description.}}{2059}{figure.683.9}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {683.10}{\ignorespaces : The Management Toolbar's Table view type list the content's information in individual columns.}}{2059}{figure.683.10}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {683.11}{\ignorespaces : The Management Toolbar offers three view type options.}}{2060}{figure.683.11}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {683.7}Creation Menu}{2060}{section.683.7}\protected@file@percent }
\newlabel{creation-menu}{{683.7}{2060}{Creation Menu}{section.683.7}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {683.12}{\ignorespaces : The Management Toolbar lets you optionally add a Creation Menu for creating new entities.}}{2061}{figure.683.12}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {683.8}Related Topics}{2061}{section.683.8}\protected@file@percent }
\newlabel{related-topics-28}{{683.8}{2061}{Related Topics}{section.683.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {684}Clay Navigation Bars}{2063}{chapter.684}\protected@file@percent }
\newlabel{clay-navigation-bars}{{684}{2063}{Clay Navigation Bars}{chapter.684}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {684.1}{\ignorespaces You can include navigation bars in your apps.}}{2063}{figure.684.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {684.2}{\ignorespaces Navigation bars can be inverted if you prefer.}}{2063}{figure.684.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {684.1}Related Topics}{2064}{section.684.1}\protected@file@percent }
\newlabel{related-topics-29}{{684.1}{2064}{Related Topics}{section.684.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {685}Clay Progress Bars}{2065}{chapter.685}\protected@file@percent }
\newlabel{clay-progress-bars}{{685}{2065}{Clay Progress Bars}{chapter.685}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {685.1}{\ignorespaces You can include progress bars in your apps.}}{2065}{figure.685.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {685.2}{\ignorespaces warning progress bars indicate that the progress has not completed due to an error.}}{2065}{figure.685.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {685.3}{\ignorespaces The complete progress bar indicates the progress is complete.}}{2066}{figure.685.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {685.1}Related Topics}{2066}{section.685.1}\protected@file@percent }
\newlabel{related-topics-30}{{685.1}{2066}{Related Topics}{section.685.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {686}Clay Stickers}{2067}{chapter.686}\protected@file@percent }
\newlabel{clay-stickers}{{686}{2067}{Clay Stickers}{chapter.686}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {686.1}{\ignorespaces You can include stickers in your apps.}}{2067}{figure.686.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {686.2}{\ignorespaces Stickers can include icons.}}{2067}{figure.686.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {686.3}{\ignorespaces You can also have circle stickers.}}{2068}{figure.686.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {686.4}{\ignorespaces You can specify the position of the sticker within a container.}}{2068}{figure.686.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {686.1}Related Topics}{2068}{section.686.1}\protected@file@percent }
\newlabel{related-topics-31}{{686.1}{2068}{Related Topics}{section.686.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {687}Using the Chart Taglib in Your Portlets}{2069}{chapter.687}\protected@file@percent }
\newlabel{using-the-chart-taglib-in-your-portlets}{{687}{2069}{Using the Chart Taglib in Your Portlets}{chapter.687}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {687.1}{\ignorespaces You can create many different types of charts with the chart taglibs.}}{2070}{figure.687.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {688}Bar Charts}{2071}{chapter.688}\protected@file@percent }
\newlabel{bar-charts}{{688}{2071}{Bar Charts}{chapter.688}{}}
\@writefile{toc}{\contentsline {section}{\numberline {688.1}Related Topics}{2071}{section.688.1}\protected@file@percent }
\newlabel{related-topics-32}{{688.1}{2071}{Related Topics}{section.688.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {688.1}{\ignorespaces A bar chart models the data in bars.}}{2072}{figure.688.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {689}Line Charts}{2073}{chapter.689}\protected@file@percent }
\newlabel{line-charts}{{689}{2073}{Line Charts}{chapter.689}{}}
\@writefile{toc}{\contentsline {section}{\numberline {689.1}Related Topics}{2073}{section.689.1}\protected@file@percent }
\newlabel{related-topics-33}{{689.1}{2073}{Related Topics}{section.689.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {689.1}{\ignorespaces A Line chart displays the data linearly.}}{2074}{figure.689.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {690}Scatter Charts}{2075}{chapter.690}\protected@file@percent }
\newlabel{scatter-charts}{{690}{2075}{Scatter Charts}{chapter.690}{}}
\@writefile{toc}{\contentsline {section}{\numberline {690.1}Related Topics}{2075}{section.690.1}\protected@file@percent }
\newlabel{related-topics-34}{{690.1}{2075}{Related Topics}{section.690.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {690.1}{\ignorespaces A scatter chart models the data as individual points.}}{2076}{figure.690.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {691}Spline Charts}{2077}{chapter.691}\protected@file@percent }
\newlabel{spline-charts}{{691}{2077}{Spline Charts}{chapter.691}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {691.1}{\ignorespaces A spline chart connects points of data with a smooth curve.}}{2078}{figure.691.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {691.1}Related Topics}{2078}{section.691.1}\protected@file@percent }
\newlabel{related-topics-35}{{691.1}{2078}{Related Topics}{section.691.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {691.2}{\ignorespaces An area spline chart highlights the area under the spline curve.}}{2079}{figure.691.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {692}Step Charts}{2081}{chapter.692}\protected@file@percent }
\newlabel{step-charts}{{692}{2081}{Step Charts}{chapter.692}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {692.1}{\ignorespaces A step chart steps between the points of data, resembling steps.}}{2082}{figure.692.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {692.1}Related Topics}{2082}{section.692.1}\protected@file@percent }
\newlabel{related-topics-36}{{692.1}{2082}{Related Topics}{section.692.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {692.2}{\ignorespaces An area step chart highlights the area covered by a step graph.}}{2083}{figure.692.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {693}Combination Charts}{2085}{chapter.693}\protected@file@percent }
\newlabel{combination-charts}{{693}{2085}{Combination Charts}{chapter.693}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {693.1}{\ignorespaces A combination chart displays a variety of data set types.}}{2086}{figure.693.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {693.1}Related Topics}{2086}{section.693.1}\protected@file@percent }
\newlabel{related-topics-37}{{693.1}{2086}{Related Topics}{section.693.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {694}Donut Charts}{2087}{chapter.694}\protected@file@percent }
\newlabel{donut-charts}{{694}{2087}{Donut Charts}{chapter.694}{}}
\@writefile{toc}{\contentsline {section}{\numberline {694.1}Related Topics}{2087}{section.694.1}\protected@file@percent }
\newlabel{related-topics-38}{{694.1}{2087}{Related Topics}{section.694.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {694.1}{\ignorespaces A donut chart is similar to a pie chart, but it has a hole in the center.}}{2088}{figure.694.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {695}Gauge Charts}{2089}{chapter.695}\protected@file@percent }
\newlabel{gauge-charts}{{695}{2089}{Gauge Charts}{chapter.695}{}}
\@writefile{toc}{\contentsline {section}{\numberline {695.1}Related Topics}{2089}{section.695.1}\protected@file@percent }
\newlabel{related-topics-39}{{695.1}{2089}{Related Topics}{section.695.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {695.1}{\ignorespaces A gauge chart shows where percentage-based data falls over a given range.}}{2090}{figure.695.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {696}Pie Charts}{2091}{chapter.696}\protected@file@percent }
\newlabel{pie-charts}{{696}{2091}{Pie Charts}{chapter.696}{}}
\@writefile{toc}{\contentsline {section}{\numberline {696.1}Related Topics}{2091}{section.696.1}\protected@file@percent }
\newlabel{related-topics-40}{{696.1}{2091}{Related Topics}{section.696.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {696.1}{\ignorespaces A pie chart models percentage-based data as individual slices of pie.}}{2092}{figure.696.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {697}Geomap Charts}{2093}{chapter.697}\protected@file@percent }
\newlabel{geomap-charts}{{697}{2093}{Geomap Charts}{chapter.697}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {697.1}{\ignorespaces A Geomap chart displays a heatmap representing the data.}}{2094}{figure.697.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {697.2}{\ignorespaces Geomap charts can be customized to fit the look and feel you desire.}}{2095}{figure.697.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {697.1}Related Topics}{2096}{section.697.1}\protected@file@percent }
\newlabel{related-topics-41}{{697.1}{2096}{Related Topics}{section.697.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {698}Predictive Charts}{2097}{chapter.698}\protected@file@percent }
\newlabel{predictive-charts}{{698}{2097}{Predictive Charts}{chapter.698}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {698.1}{\ignorespaces Predicted/forecasted data is surrounded by a highlighted area of possible values.}}{2097}{figure.698.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {698.2}{\ignorespaces A predictive chart lets you visualize estimated future data alongside existing data.}}{2099}{figure.698.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {698.1}Related Topics}{2099}{section.698.1}\protected@file@percent }
\newlabel{related-topics-42}{{698.1}{2099}{Related Topics}{section.698.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {699}Refreshing Charts to Reflect Real Time Data}{2101}{chapter.699}\protected@file@percent }
\newlabel{refreshing-charts-to-reflect-real-time-data}{{699}{2101}{Refreshing Charts to Reflect Real Time Data}{chapter.699}{}}
\@writefile{toc}{\contentsline {section}{\numberline {699.1}Related Topics}{2101}{section.699.1}\protected@file@percent }
\newlabel{related-topics-43}{{699.1}{2101}{Related Topics}{section.699.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {699.1}{\ignorespaces The polling interval property lets you refresh charts at a given interval to reflect real time data.}}{2102}{figure.699.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {700}Using AUI Taglibs}{2103}{chapter.700}\protected@file@percent }
\newlabel{using-aui-taglibs}{{700}{2103}{Using AUI Taglibs}{chapter.700}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {701}Building Forms with AUI Tags}{2105}{chapter.701}\protected@file@percent }
\newlabel{building-forms-with-aui-tags}{{701}{2105}{Building Forms with AUI Tags}{chapter.701}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {701.1}{\ignorespaces The AUI tags provide everything you need to build forms for your applications.}}{2106}{figure.701.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {701.2}{\ignorespaces The AUI tags also provide validation for form fields.}}{2107}{figure.701.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {701.1}Related Topics}{2108}{section.701.1}\protected@file@percent }
\newlabel{related-topics-44}{{701.1}{2108}{Related Topics}{section.701.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {702}liferay-npm-bundler}{2109}{chapter.702}\protected@file@percent }
\newlabel{liferay-npm-bundler}{{702}{2109}{liferay-npm-bundler}{chapter.702}{}}
\@writefile{toc}{\contentsline {section}{\numberline {702.1}How the Liferay npm Bundler Works Internally}{2109}{section.702.1}\protected@file@percent }
\newlabel{how-the-liferay-npm-bundler-works-internally}{{702.1}{2109}{How the Liferay npm Bundler Works Internally}{section.702.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {703}Understanding the \texttt {.npmbundlerrc}'s Structure}{2111}{chapter.703}\protected@file@percent }
\newlabel{understanding-the-.npmbundlerrcs-structure}{{703}{2111}{\texorpdfstring {Understanding the \texttt {.npmbundlerrc}'s Structure}{Understanding the .npmbundlerrc's Structure}}{chapter.703}{}}
\@writefile{toc}{\contentsline {section}{\numberline {703.1}The Structure}{2111}{section.703.1}\protected@file@percent }
\newlabel{the-structure}{{703.1}{2111}{The Structure}{section.703.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {703.2}Standard Configuration Options}{2113}{section.703.2}\protected@file@percent }
\newlabel{standard-configuration-options}{{703.2}{2113}{Standard Configuration Options}{section.703.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {703.3}Package Processing Options}{2114}{section.703.3}\protected@file@percent }
\newlabel{package-processing-options}{{703.3}{2114}{Package Processing Options}{section.703.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {703.4}OSGi Bundle Creation Options}{2115}{section.703.4}\protected@file@percent }
\newlabel{osgi-bundle-creation-options}{{703.4}{2115}{OSGi Bundle Creation Options}{section.703.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {704}How the Default Preset Configures the liferay-npm-bundler}{2119}{chapter.704}\protected@file@percent }
\newlabel{how-the-default-preset-configures-the-liferay-npm-bundler}{{704}{2119}{How the Default Preset Configures the liferay-npm-bundler}{chapter.704}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {705}The Structure of OSGi Bundles Containing npm Packages}{2121}{chapter.705}\protected@file@percent }
\newlabel{the-structure-of-osgi-bundles-containing-npm-packages}{{705}{2121}{The Structure of OSGi Bundles Containing npm Packages}{chapter.705}{}}
\@writefile{toc}{\contentsline {section}{\numberline {705.1}Inline JavaScript packages}{2122}{section.705.1}\protected@file@percent }
\newlabel{inline-javascript-packages}{{705.1}{2122}{Inline JavaScript packages}{section.705.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {706}How the Liferay npm Bundler Publishes npm Packages}{2123}{chapter.706}\protected@file@percent }
\newlabel{how-the-liferay-npm-bundler-publishes-npm-packages}{{706}{2123}{How the Liferay npm Bundler Publishes npm Packages}{chapter.706}{}}
\@writefile{toc}{\contentsline {section}{\numberline {706.1}Package De-duplication}{2124}{section.706.1}\protected@file@percent }
\newlabel{package-de-duplication}{{706.1}{2124}{Package De-duplication}{section.706.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {706.2}Isolated Package Dependencies}{2125}{section.706.2}\protected@file@percent }
\newlabel{isolated-package-dependencies}{{706.2}{2125}{Isolated Package Dependencies}{section.706.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {706.3}De-duplication through Importing}{2127}{section.706.3}\protected@file@percent }
\newlabel{de-duplication-through-importing}{{706.3}{2127}{De-duplication through Importing}{section.706.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {706.4}Strategies When Importing Packages}{2129}{section.706.4}\protected@file@percent }
\newlabel{strategies-when-importing-packages}{{706.4}{2129}{Strategies When Importing Packages}{section.706.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {707}Understanding How liferay-npm-bundler Formats JavaScript Modules for AMD}{2131}{chapter.707}\protected@file@percent }
\newlabel{understanding-how-liferay-npm-bundler-formats-javascript-modules-for-amd}{{707}{2131}{Understanding How liferay-npm-bundler Formats JavaScript Modules for AMD}{chapter.707}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {708}Understanding How Liferay AMD Loader Configuration is Exported}{2135}{chapter.708}\protected@file@percent }
\newlabel{understanding-how-liferay-amd-loader-configuration-is-exported}{{708}{2135}{Understanding How Liferay AMD Loader Configuration is Exported}{chapter.708}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {709}What Changed Between Liferay npm Bundler 1.x and 2.x}{2139}{chapter.709}\protected@file@percent }
\newlabel{what-changed-between-liferay-npm-bundler-1.x-and-2.x}{{709}{2139}{What Changed Between Liferay npm Bundler 1.x and 2.x}{chapter.709}{}}
\@writefile{toc}{\contentsline {section}{\numberline {709.1}Automatically Formatting Modules for AMD}{2139}{section.709.1}\protected@file@percent }
\newlabel{automatically-formatting-modules-for-amd}{{709.1}{2139}{Automatically Formatting Modules for AMD}{section.709.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {709.2}Isolating Project Dependencies}{2139}{section.709.2}\protected@file@percent }
\newlabel{isolating-project-dependencies}{{709.2}{2139}{Isolating Project Dependencies}{section.709.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {709.3}Improved Peer Dependency Support}{2139}{section.709.3}\protected@file@percent }
\newlabel{improved-peer-dependency-support}{{709.3}{2139}{Improved Peer Dependency Support}{section.709.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {709.4}Manually De-duplicating Through Importing}{2140}{section.709.4}\protected@file@percent }
\newlabel{manually-de-duplicating-through-importing}{{709.4}{2140}{Manually De-duplicating Through Importing}{section.709.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {710}Understanding liferay-npm-bundler's Loaders}{2141}{chapter.710}\protected@file@percent }
\newlabel{understanding-liferay-npm-bundlers-loaders}{{710}{2141}{Understanding liferay-npm-bundler's Loaders}{chapter.710}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {711}Default liferay-npm-bundler Loaders}{2143}{chapter.711}\protected@file@percent }
\newlabel{default-liferay-npm-bundler-loaders}{{711}{2143}{Default liferay-npm-bundler Loaders}{chapter.711}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {712}Liferay JavaScript APIs}{2145}{chapter.712}\protected@file@percent }
\newlabel{liferay-javascript-apis}{{712}{2145}{Liferay JavaScript APIs}{chapter.712}{}}
\gdef \LT@xxxv {\LT@entry
{1}{154.56912pt}\LT@entry
{1}{160.56912pt}\LT@entry
{1}{154.56912pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {713}Accessing ThemeDisplay Information}{2147}{chapter.713}\protected@file@percent }
\newlabel{accessing-themedisplay-information}{{713}{2147}{Accessing ThemeDisplay Information}{chapter.713}{}}
\gdef \LT@xxxvi {\LT@entry
{1}{154.56912pt}\LT@entry
{1}{160.56912pt}\LT@entry
{1}{154.56912pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {714}Working with URLs in JavaScript}{2151}{chapter.714}\protected@file@percent }
\newlabel{working-with-urls-in-javascript}{{714}{2151}{Working with URLs in JavaScript}{chapter.714}{}}
\@writefile{toc}{\contentsline {section}{\numberline {714.1}Portlet URL Methods}{2151}{section.714.1}\protected@file@percent }
\newlabel{portlet-url-methods}{{714.1}{2151}{Portlet URL Methods}{section.714.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {714.2}Liferay Util PortletURL}{2152}{section.714.2}\protected@file@percent }
\newlabel{liferay-util-portleturl}{{714.2}{2152}{Liferay Util PortletURL}{section.714.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {714.3}Liferay AuthToken}{2152}{section.714.3}\protected@file@percent }
\newlabel{liferay-authtoken}{{714.3}{2152}{Liferay AuthToken}{section.714.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {714.4}Liferay CurrentURL}{2152}{section.714.4}\protected@file@percent }
\newlabel{liferay-currenturl}{{714.4}{2152}{Liferay CurrentURL}{section.714.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {714.5}Liferay CurrentURLEncoded}{2153}{section.714.5}\protected@file@percent }
\newlabel{liferay-currenturlencoded}{{714.5}{2153}{Liferay CurrentURLEncoded}{section.714.5}{}}
\gdef \LT@xxxvii {\LT@entry
{1}{154.56912pt}\LT@entry
{1}{160.56912pt}\LT@entry
{1}{154.56912pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {715}Liferay DXP JavaScript Utilities}{2155}{chapter.715}\protected@file@percent }
\newlabel{liferay-dxp-javascript-utilities}{{715}{2155}{Liferay DXP JavaScript Utilities}{chapter.715}{}}
\@writefile{toc}{\contentsline {section}{\numberline {715.1}Retrieve Browser Information}{2155}{section.715.1}\protected@file@percent }
\newlabel{retrieve-browser-information}{{715.1}{2155}{Retrieve Browser Information}{section.715.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {715.2}Format XML}{2156}{section.715.2}\protected@file@percent }
\newlabel{format-xml}{{715.2}{2156}{Format XML}{section.715.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {715.3}Format Storage Size}{2157}{section.715.3}\protected@file@percent }
\newlabel{format-storage-size}{{715.3}{2157}{Format Storage Size}{section.715.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {715.4}Store and Retrieve Session Form data}{2157}{section.715.4}\protected@file@percent }
\newlabel{store-and-retrieve-session-form-data}{{715.4}{2157}{Store and Retrieve Session Form data}{section.715.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {716}Invoking Liferay Services}{2159}{chapter.716}\protected@file@percent }
\newlabel{invoking-liferay-services}{{716}{2159}{Invoking Liferay Services}{chapter.716}{}}
\@writefile{toc}{\contentsline {section}{\numberline {716.1}Invoking Web Services via JavaScript}{2159}{section.716.1}\protected@file@percent }
\newlabel{invoking-web-services-via-javascript}{{716.1}{2159}{Invoking Web Services via JavaScript}{section.716.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {716.2}Batching Requests}{2160}{section.716.2}\protected@file@percent }
\newlabel{batching-requests}{{716.2}{2160}{Batching Requests}{section.716.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {716.3}Nesting Requests}{2161}{section.716.3}\protected@file@percent }
\newlabel{nesting-requests}{{716.3}{2161}{Nesting Requests}{section.716.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {716.4}Filtering Results}{2162}{section.716.4}\protected@file@percent }
\newlabel{filtering-results}{{716.4}{2162}{Filtering Results}{section.716.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {716.5}Inner Parameters}{2163}{section.716.5}\protected@file@percent }
\newlabel{inner-parameters}{{716.5}{2163}{Inner Parameters}{section.716.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {717}Handling AJAX Requests with \texttt {Liferay.Util.fetch}}{2165}{chapter.717}\protected@file@percent }
\newlabel{handling-ajax-requests-with-liferay.util.fetch}{{717}{2165}{\texorpdfstring {Handling AJAX Requests with \texttt {Liferay.Util.fetch}}{Handling AJAX Requests with Liferay.Util.fetch}}{chapter.717}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {718}Working with Addresses}{2167}{chapter.718}\protected@file@percent }
\newlabel{working-with-addresses}{{718}{2167}{Working with Addresses}{chapter.718}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {719}FreeMarker Taglib Macros}{2169}{chapter.719}\protected@file@percent }
\newlabel{freemarker-taglib-macros}{{719}{2169}{FreeMarker Taglib Macros}{chapter.719}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {720}Setting up Your npm Environment}{2173}{chapter.720}\protected@file@percent }
\newlabel{setting-up-your-npm-environment}{{720}{2173}{Setting up Your npm Environment}{chapter.720}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {721}Sitemap Page Configuration Options}{2175}{chapter.721}\protected@file@percent }
\newlabel{sitemap-page-configuration-options}{{721}{2175}{Sitemap Page Configuration Options}{chapter.721}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {722}CKEditor Plugin Reference Guide}{2177}{chapter.722}\protected@file@percent }
\newlabel{ckeditor-plugin-reference-guide}{{722}{2177}{CKEditor Plugin Reference Guide}{chapter.722}{}}
\gdef \LT@xxxviii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@xxxix {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {723}Fully Qualified Portlet IDs}{2181}{chapter.723}\protected@file@percent }
\newlabel{fully-qualified-portlet-ids}{{723}{2181}{Fully Qualified Portlet IDs}{chapter.723}{}}
\gdef \LT@xl {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@xli {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@xlii {\LT@entry
{3}{64.40727pt}\LT@entry
{3}{271.06067pt}}
\gdef \LT@xliii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@xliv {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@xlv {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@xlvi {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@xlvii {\LT@entry
{1}{154.56912pt}\LT@entry
{1}{160.56912pt}\LT@entry
{1}{154.56912pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {724}Available SPA Lifecycle Events}{2185}{chapter.724}\protected@file@percent }
\newlabel{available-spa-lifecycle-events}{{724}{2185}{Available SPA Lifecycle Events}{chapter.724}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {724.1}{\ignorespaces You can leverage SPA lifecycle events in your apps.}}{2186}{figure.724.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {725}Theme Anatomy Reference Guide}{2187}{chapter.725}\protected@file@percent }
\newlabel{theme-anatomy-reference-guide}{{725}{2187}{Theme Anatomy Reference Guide}{chapter.725}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.1}Theme Files}{2188}{section.725.1}\protected@file@percent }
\newlabel{theme-files}{{725.1}{2188}{Theme Files}{section.725.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.2}\_clay\_custom.scss}{2188}{section.725.2}\protected@file@percent }
\newlabel{clay_custom.scss}{{725.2}{2188}{\_clay\_custom.scss}{section.725.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.3}\_clay\_variables.scss}{2188}{section.725.3}\protected@file@percent }
\newlabel{clay_variables.scss}{{725.3}{2188}{\_clay\_variables.scss}{section.725.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.4}\_custom.scss}{2188}{section.725.4}\protected@file@percent }
\newlabel{custom.scss}{{725.4}{2188}{\_custom.scss}{section.725.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.5}\_liferay\_variables\_custom.scss}{2188}{section.725.5}\protected@file@percent }
\newlabel{liferay_variables_custom.scss}{{725.5}{2188}{\_liferay\_variables\_custom.scss}{section.725.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.6}init\_custom.ftl}{2188}{section.725.6}\protected@file@percent }
\newlabel{init_custom.ftl}{{725.6}{2188}{init\_custom.ftl}{section.725.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.7}navigation.ftl}{2189}{section.725.7}\protected@file@percent }
\newlabel{navigation.ftl}{{725.7}{2189}{navigation.ftl}{section.725.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.8}portal\_normal.ftl}{2189}{section.725.8}\protected@file@percent }
\newlabel{portal_normal.ftl}{{725.8}{2189}{portal\_normal.ftl}{section.725.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.9}portal\_pop\_up.ftl}{2189}{section.725.9}\protected@file@percent }
\newlabel{portal_pop_up.ftl}{{725.9}{2189}{portal\_pop\_up.ftl}{section.725.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.10}portlet.ftl}{2189}{section.725.10}\protected@file@percent }
\newlabel{portlet.ftl}{{725.10}{2189}{portlet.ftl}{section.725.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.11}liferay-theme.json}{2189}{section.725.11}\protected@file@percent }
\newlabel{liferay-theme.json}{{725.11}{2189}{liferay-theme.json}{section.725.11}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.12}package.json}{2189}{section.725.12}\protected@file@percent }
\newlabel{package.json}{{725.12}{2189}{package.json}{section.725.12}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.13}main.js}{2189}{section.725.13}\protected@file@percent }
\newlabel{main.js}{{725.13}{2189}{main.js}{section.725.13}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.14}liferay-look-and-feel.xml}{2189}{section.725.14}\protected@file@percent }
\newlabel{liferay-look-and-feel.xml}{{725.14}{2189}{liferay-look-and-feel.xml}{section.725.14}{}}
\@writefile{toc}{\contentsline {section}{\numberline {725.15}liferay-plugin-package.properties}{2190}{section.725.15}\protected@file@percent }
\newlabel{liferay-plugin-package.properties}{{725.15}{2190}{liferay-plugin-package.properties}{section.725.15}{}}
\gdef \LT@xlviii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {726}Freemarker Variable Reference Guide}{2191}{chapter.726}\protected@file@percent }
\newlabel{freemarker-variable-reference-guide}{{726}{2191}{Freemarker Variable Reference Guide}{chapter.726}{}}
\gdef \LT@xlix {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@l {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@li {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@lii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@liii {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@liv {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\gdef \LT@lv {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {727}Gradle Plugins}{2197}{chapter.727}\protected@file@percent }
\newlabel{gradle-plugins}{{727}{2197}{Gradle Plugins}{chapter.727}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {728}App Javadoc Builder Gradle Plugin}{2199}{chapter.728}\protected@file@percent }
\newlabel{app-javadoc-builder-gradle-plugin}{{728}{2199}{App Javadoc Builder Gradle Plugin}{chapter.728}{}}
\@writefile{toc}{\contentsline {section}{\numberline {728.1}Usage}{2199}{section.728.1}\protected@file@percent }
\newlabel{usage}{{728.1}{2199}{Usage}{section.728.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {728.2}Project Extension}{2199}{section.728.2}\protected@file@percent }
\newlabel{project-extension}{{728.2}{2199}{Project Extension}{section.728.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {728.3}Tasks}{2200}{section.728.3}\protected@file@percent }
\newlabel{tasks}{{728.3}{2200}{Tasks}{section.728.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {729}Baseline Gradle Plugin}{2201}{chapter.729}\protected@file@percent }
\newlabel{baseline-gradle-plugin}{{729}{2201}{Baseline Gradle Plugin}{chapter.729}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.1}Usage}{2201}{section.729.1}\protected@file@percent }
\newlabel{usage-1}{{729.1}{2201}{Usage}{section.729.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.2}Project Extension}{2202}{section.729.2}\protected@file@percent }
\newlabel{project-extension-1}{{729.2}{2202}{Project Extension}{section.729.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.3}Tasks}{2202}{section.729.3}\protected@file@percent }
\newlabel{tasks-1}{{729.3}{2202}{Tasks}{section.729.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.4}BaselineTask}{2202}{section.729.4}\protected@file@percent }
\newlabel{baselinetask}{{729.4}{2202}{BaselineTask}{section.729.4}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2202}{section.729.4}\protected@file@percent }
\newlabel{task-properties}{{729.4}{2202}{Task Properties}{section.729.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.5}Helper Tasks}{2203}{section.729.5}\protected@file@percent }
\newlabel{helper-tasks}{{729.5}{2203}{Helper Tasks}{section.729.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.6}Additional Configuration}{2203}{section.729.6}\protected@file@percent }
\newlabel{additional-configuration}{{729.6}{2203}{Additional Configuration}{section.729.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.7}Baseline Dependency}{2204}{section.729.7}\protected@file@percent }
\newlabel{baseline-dependency}{{729.7}{2204}{Baseline Dependency}{section.729.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {729.8}System Properties}{2204}{section.729.8}\protected@file@percent }
\newlabel{system-properties}{{729.8}{2204}{System Properties}{section.729.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {730}Change Log Builder Gradle Plugin}{2205}{chapter.730}\protected@file@percent }
\newlabel{change-log-builder-gradle-plugin}{{730}{2205}{Change Log Builder Gradle Plugin}{chapter.730}{}}
\@writefile{toc}{\contentsline {section}{\numberline {730.1}Usage}{2205}{section.730.1}\protected@file@percent }
\newlabel{usage-2}{{730.1}{2205}{Usage}{section.730.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {730.2}Tasks}{2206}{section.730.2}\protected@file@percent }
\newlabel{tasks-2}{{730.2}{2206}{Tasks}{section.730.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {730.3}BuildChangeLogTask}{2206}{section.730.3}\protected@file@percent }
\newlabel{buildchangelogtask}{{730.3}{2206}{BuildChangeLogTask}{section.730.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2206}{section.730.3}\protected@file@percent }
\newlabel{task-properties-1}{{730.3}{2206}{Task Properties}{section.730.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2206}{section.730.3}\protected@file@percent }
\newlabel{task-methods}{{730.3}{2206}{Task Methods}{section.730.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {731}CSS Builder Gradle Plugin}{2207}{chapter.731}\protected@file@percent }
\newlabel{css-builder-gradle-plugin}{{731}{2207}{CSS Builder Gradle Plugin}{chapter.731}{}}
\@writefile{toc}{\contentsline {section}{\numberline {731.1}Usage}{2207}{section.731.1}\protected@file@percent }
\newlabel{usage-3}{{731.1}{2207}{Usage}{section.731.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {731.2}Tasks}{2207}{section.731.2}\protected@file@percent }
\newlabel{tasks-3}{{731.2}{2207}{Tasks}{section.731.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {731.3}BuildCSSTask}{2208}{section.731.3}\protected@file@percent }
\newlabel{buildcsstask}{{731.3}{2208}{BuildCSSTask}{section.731.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2208}{section.731.3}\protected@file@percent }
\newlabel{task-properties-2}{{731.3}{2208}{Task Properties}{section.731.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2209}{section.731.3}\protected@file@percent }
\newlabel{task-methods-1}{{731.3}{2209}{Task Methods}{section.731.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {731.4}Additional Configuration}{2209}{section.731.4}\protected@file@percent }
\newlabel{additional-configuration-1}{{731.4}{2209}{Additional Configuration}{section.731.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {731.5}Liferay CSS Builder Dependency}{2209}{section.731.5}\protected@file@percent }
\newlabel{liferay-css-builder-dependency}{{731.5}{2209}{Liferay CSS Builder Dependency}{section.731.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {731.6}Liferay Frontend Common CSS Dependency}{2209}{section.731.6}\protected@file@percent }
\newlabel{liferay-frontend-common-css-dependency}{{731.6}{2209}{Liferay Frontend Common CSS Dependency}{section.731.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {732}DB Support Gradle Plugin}{2211}{chapter.732}\protected@file@percent }
\newlabel{db-support-gradle-plugin}{{732}{2211}{DB Support Gradle Plugin}{chapter.732}{}}
\@writefile{toc}{\contentsline {section}{\numberline {732.1}Usage}{2211}{section.732.1}\protected@file@percent }
\newlabel{usage-4}{{732.1}{2211}{Usage}{section.732.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {732.2}Tasks}{2212}{section.732.2}\protected@file@percent }
\newlabel{tasks-4}{{732.2}{2212}{Tasks}{section.732.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {732.3}CleanServiceBuilderTask}{2212}{section.732.3}\protected@file@percent }
\newlabel{cleanservicebuildertask}{{732.3}{2212}{CleanServiceBuilderTask}{section.732.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2212}{section.732.3}\protected@file@percent }
\newlabel{task-properties-3}{{732.3}{2212}{Task Properties}{section.732.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {732.4}Additional Configuration}{2212}{section.732.4}\protected@file@percent }
\newlabel{additional-configuration-2}{{732.4}{2212}{Additional Configuration}{section.732.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {732.5}JDBC Drivers Dependency}{2213}{section.732.5}\protected@file@percent }
\newlabel{jdbc-drivers-dependency}{{732.5}{2213}{JDBC Drivers Dependency}{section.732.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {732.6}Liferay DB Support Dependency}{2213}{section.732.6}\protected@file@percent }
\newlabel{liferay-db-support-dependency}{{732.6}{2213}{Liferay DB Support Dependency}{section.732.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {733}Dependency Checker Gradle Plugin}{2215}{chapter.733}\protected@file@percent }
\newlabel{dependency-checker-gradle-plugin}{{733}{2215}{Dependency Checker Gradle Plugin}{chapter.733}{}}
\@writefile{toc}{\contentsline {section}{\numberline {733.1}Usage}{2215}{section.733.1}\protected@file@percent }
\newlabel{usage-5}{{733.1}{2215}{Usage}{section.733.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {733.2}Project Extension}{2215}{section.733.2}\protected@file@percent }
\newlabel{project-extension-2}{{733.2}{2215}{Project Extension}{section.733.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {733.3}Additional Configuration}{2216}{section.733.3}\protected@file@percent }
\newlabel{additional-configuration-3}{{733.3}{2216}{Additional Configuration}{section.733.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {733.4}Project Properties}{2216}{section.733.4}\protected@file@percent }
\newlabel{project-properties}{{733.4}{2216}{Project Properties}{section.733.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {734}Deployment Helper Gradle Plugin}{2217}{chapter.734}\protected@file@percent }
\newlabel{deployment-helper-gradle-plugin}{{734}{2217}{Deployment Helper Gradle Plugin}{chapter.734}{}}
\@writefile{toc}{\contentsline {section}{\numberline {734.1}Usage}{2217}{section.734.1}\protected@file@percent }
\newlabel{usage-6}{{734.1}{2217}{Usage}{section.734.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {734.2}Tasks}{2217}{section.734.2}\protected@file@percent }
\newlabel{tasks-5}{{734.2}{2217}{Tasks}{section.734.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {734.3}BuildDeploymentHelperTask}{2218}{section.734.3}\protected@file@percent }
\newlabel{builddeploymenthelpertask}{{734.3}{2218}{BuildDeploymentHelperTask}{section.734.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2218}{section.734.3}\protected@file@percent }
\newlabel{task-properties-4}{{734.3}{2218}{Task Properties}{section.734.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2218}{section.734.3}\protected@file@percent }
\newlabel{task-methods-2}{{734.3}{2218}{Task Methods}{section.734.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {734.4}Additional Configuration}{2218}{section.734.4}\protected@file@percent }
\newlabel{additional-configuration-4}{{734.4}{2218}{Additional Configuration}{section.734.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {734.5}Liferay Deployment Helper Dependency}{2218}{section.734.5}\protected@file@percent }
\newlabel{liferay-deployment-helper-dependency}{{734.5}{2218}{Liferay Deployment Helper Dependency}{section.734.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {735}Go Gradle Plugin}{2219}{chapter.735}\protected@file@percent }
\newlabel{go-gradle-plugin}{{735}{2219}{Go Gradle Plugin}{chapter.735}{}}
\@writefile{toc}{\contentsline {section}{\numberline {735.1}Usage}{2219}{section.735.1}\protected@file@percent }
\newlabel{usage-7}{{735.1}{2219}{Usage}{section.735.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {735.2}Project Extension}{2219}{section.735.2}\protected@file@percent }
\newlabel{project-extension-3}{{735.2}{2219}{Project Extension}{section.735.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {735.3}Tasks}{2219}{section.735.3}\protected@file@percent }
\newlabel{tasks-6}{{735.3}{2219}{Tasks}{section.735.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {735.4}DownloadGoTask}{2220}{section.735.4}\protected@file@percent }
\newlabel{downloadgotask}{{735.4}{2220}{DownloadGoTask}{section.735.4}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2220}{section.735.4}\protected@file@percent }
\newlabel{task-properties-5}{{735.4}{2220}{Task Properties}{section.735.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {735.5}ExecuteGoTask}{2220}{section.735.5}\protected@file@percent }
\newlabel{executegotask}{{735.5}{2220}{ExecuteGoTask}{section.735.5}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2220}{section.735.5}\protected@file@percent }
\newlabel{task-properties-6}{{735.5}{2220}{Task Properties}{section.735.5}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2221}{section.735.5}\protected@file@percent }
\newlabel{task-methods-3}{{735.5}{2221}{Task Methods}{section.735.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {735.6}go\({command}\)\{programName\} Task}{2221}{section.735.6}\protected@file@percent }
\newlabel{gocommandprogramname-task}{{735.6}{2221}{\texorpdfstring {go\({command}\)\{programName\} Task}{go\{command\}\{programName\} Task}}{section.735.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {736}Gulp Gradle Plugin}{2223}{chapter.736}\protected@file@percent }
\newlabel{gulp-gradle-plugin}{{736}{2223}{Gulp Gradle Plugin}{chapter.736}{}}
\@writefile{toc}{\contentsline {section}{\numberline {736.1}Usage}{2223}{section.736.1}\protected@file@percent }
\newlabel{usage-8}{{736.1}{2223}{Usage}{section.736.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {736.2}Tasks}{2223}{section.736.2}\protected@file@percent }
\newlabel{tasks-7}{{736.2}{2223}{Tasks}{section.736.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {736.3}ExecuteGulpTask}{2223}{section.736.3}\protected@file@percent }
\newlabel{executegulptask}{{736.3}{2223}{ExecuteGulpTask}{section.736.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2224}{section.736.3}\protected@file@percent }
\newlabel{task-properties-7}{{736.3}{2224}{Task Properties}{section.736.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {737}Jasper JSPC Gradle Plugin}{2225}{chapter.737}\protected@file@percent }
\newlabel{jasper-jspc-gradle-plugin}{{737}{2225}{Jasper JSPC Gradle Plugin}{chapter.737}{}}
\@writefile{toc}{\contentsline {section}{\numberline {737.1}Usage}{2225}{section.737.1}\protected@file@percent }
\newlabel{usage-9}{{737.1}{2225}{Usage}{section.737.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {737.2}Tasks}{2226}{section.737.2}\protected@file@percent }
\newlabel{tasks-8}{{737.2}{2226}{Tasks}{section.737.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {737.3}CompileJSPTask}{2226}{section.737.3}\protected@file@percent }
\newlabel{compilejsptask}{{737.3}{2226}{CompileJSPTask}{section.737.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2226}{section.737.3}\protected@file@percent }
\newlabel{task-properties-8}{{737.3}{2226}{Task Properties}{section.737.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {737.4}Additional Configuration}{2226}{section.737.4}\protected@file@percent }
\newlabel{additional-configuration-5}{{737.4}{2226}{Additional Configuration}{section.737.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {737.5}JSP Compilation Classpath}{2226}{section.737.5}\protected@file@percent }
\newlabel{jsp-compilation-classpath}{{737.5}{2226}{JSP Compilation Classpath}{section.737.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {737.6}Liferay Jasper JSPC Dependency}{2227}{section.737.6}\protected@file@percent }
\newlabel{liferay-jasper-jspc-dependency}{{737.6}{2227}{Liferay Jasper JSPC Dependency}{section.737.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {738}Javadoc Formatter Gradle Plugin}{2229}{chapter.738}\protected@file@percent }
\newlabel{javadoc-formatter-gradle-plugin}{{738}{2229}{Javadoc Formatter Gradle Plugin}{chapter.738}{}}
\@writefile{toc}{\contentsline {section}{\numberline {738.1}Usage}{2229}{section.738.1}\protected@file@percent }
\newlabel{usage-10}{{738.1}{2229}{Usage}{section.738.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {738.2}Tasks}{2230}{section.738.2}\protected@file@percent }
\newlabel{tasks-9}{{738.2}{2230}{Tasks}{section.738.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {738.3}FormatJavadocTask}{2230}{section.738.3}\protected@file@percent }
\newlabel{formatjavadoctask}{{738.3}{2230}{FormatJavadocTask}{section.738.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2230}{section.738.3}\protected@file@percent }
\newlabel{task-properties-9}{{738.3}{2230}{Task Properties}{section.738.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2230}{section.738.3}\protected@file@percent }
\newlabel{task-methods-4}{{738.3}{2230}{Task Methods}{section.738.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {738.4}Additional Configuration}{2230}{section.738.4}\protected@file@percent }
\newlabel{additional-configuration-6}{{738.4}{2230}{Additional Configuration}{section.738.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {738.5}Liferay Javadoc Formatter Dependency}{2231}{section.738.5}\protected@file@percent }
\newlabel{liferay-javadoc-formatter-dependency}{{738.5}{2231}{Liferay Javadoc Formatter Dependency}{section.738.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {738.6}System Properties}{2231}{section.738.6}\protected@file@percent }
\newlabel{system-properties-1}{{738.6}{2231}{System Properties}{section.738.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {739}JS Module Config Generator Gradle Plugin}{2233}{chapter.739}\protected@file@percent }
\newlabel{js-module-config-generator-gradle-plugin}{{739}{2233}{JS Module Config Generator Gradle Plugin}{chapter.739}{}}
\@writefile{toc}{\contentsline {section}{\numberline {739.1}Usage}{2233}{section.739.1}\protected@file@percent }
\newlabel{usage-11}{{739.1}{2233}{Usage}{section.739.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {739.2}Project Extension}{2233}{section.739.2}\protected@file@percent }
\newlabel{project-extension-4}{{739.2}{2233}{Project Extension}{section.739.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {739.3}Tasks}{2234}{section.739.3}\protected@file@percent }
\newlabel{tasks-10}{{739.3}{2234}{Tasks}{section.739.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {739.4}ConfigJSModulesTask}{2234}{section.739.4}\protected@file@percent }
\newlabel{configjsmodulestask}{{739.4}{2234}{ConfigJSModulesTask}{section.739.4}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2234}{section.739.4}\protected@file@percent }
\newlabel{task-properties-10}{{739.4}{2234}{Task Properties}{section.739.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {740}JS Transpiler Gradle Plugin}{2237}{chapter.740}\protected@file@percent }
\newlabel{js-transpiler-gradle-plugin}{{740}{2237}{JS Transpiler Gradle Plugin}{chapter.740}{}}
\@writefile{toc}{\contentsline {section}{\numberline {740.1}Usage}{2237}{section.740.1}\protected@file@percent }
\newlabel{usage-12}{{740.1}{2237}{Usage}{section.740.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {740.2}JS Transpiler Plugin}{2238}{section.740.2}\protected@file@percent }
\newlabel{js-transpiler-plugin}{{740.2}{2238}{JS Transpiler Plugin}{section.740.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {740.3}JS Transpiler Base Plugin}{2238}{section.740.3}\protected@file@percent }
\newlabel{js-transpiler-base-plugin}{{740.3}{2238}{JS Transpiler Base Plugin}{section.740.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {740.4}Tasks}{2238}{section.740.4}\protected@file@percent }
\newlabel{tasks-11}{{740.4}{2238}{Tasks}{section.740.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {740.5}TranspileJSTask}{2239}{section.740.5}\protected@file@percent }
\newlabel{transpilejstask}{{740.5}{2239}{TranspileJSTask}{section.740.5}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2239}{section.740.5}\protected@file@percent }
\newlabel{task-properties-11}{{740.5}{2239}{Task Properties}{section.740.5}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2239}{section.740.5}\protected@file@percent }
\newlabel{task-methods-5}{{740.5}{2239}{Task Methods}{section.740.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {741}JSDoc Gradle Plugin}{2241}{chapter.741}\protected@file@percent }
\newlabel{jsdoc-gradle-plugin}{{741}{2241}{JSDoc Gradle Plugin}{chapter.741}{}}
\@writefile{toc}{\contentsline {section}{\numberline {741.1}Usage}{2241}{section.741.1}\protected@file@percent }
\newlabel{usage-13}{{741.1}{2241}{Usage}{section.741.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {741.2}JSDoc Plugin}{2242}{section.741.2}\protected@file@percent }
\newlabel{jsdoc-plugin}{{741.2}{2242}{JSDoc Plugin}{section.741.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {741.3}AppJSDoc Plugin}{2242}{section.741.3}\protected@file@percent }
\newlabel{appjsdoc-plugin}{{741.3}{2242}{AppJSDoc Plugin}{section.741.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {741.4}Project Extension}{2242}{section.741.4}\protected@file@percent }
\newlabel{project-extension-5}{{741.4}{2242}{Project Extension}{section.741.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {741.5}Tasks}{2243}{section.741.5}\protected@file@percent }
\newlabel{tasks-12}{{741.5}{2243}{Tasks}{section.741.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {741.6}JSDocTask}{2243}{section.741.6}\protected@file@percent }
\newlabel{jsdoctask}{{741.6}{2243}{JSDocTask}{section.741.6}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2243}{section.741.6}\protected@file@percent }
\newlabel{task-properties-12}{{741.6}{2243}{Task Properties}{section.741.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {742}Lang Builder Gradle Plugin}{2245}{chapter.742}\protected@file@percent }
\newlabel{lang-builder-gradle-plugin}{{742}{2245}{Lang Builder Gradle Plugin}{chapter.742}{}}
\@writefile{toc}{\contentsline {section}{\numberline {742.1}Usage}{2245}{section.742.1}\protected@file@percent }
\newlabel{usage-14}{{742.1}{2245}{Usage}{section.742.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {742.2}Tasks}{2246}{section.742.2}\protected@file@percent }
\newlabel{tasks-13}{{742.2}{2246}{Tasks}{section.742.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {742.3}BuildLangTask}{2246}{section.742.3}\protected@file@percent }
\newlabel{buildlangtask}{{742.3}{2246}{BuildLangTask}{section.742.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2246}{section.742.3}\protected@file@percent }
\newlabel{task-properties-13}{{742.3}{2246}{Task Properties}{section.742.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2246}{section.742.3}\protected@file@percent }
\newlabel{task-methods-6}{{742.3}{2246}{Task Methods}{section.742.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {742.4}Additional Configuration}{2247}{section.742.4}\protected@file@percent }
\newlabel{additional-configuration-7}{{742.4}{2247}{Additional Configuration}{section.742.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {742.5}Liferay Lang Builder Dependency}{2247}{section.742.5}\protected@file@percent }
\newlabel{liferay-lang-builder-dependency}{{742.5}{2247}{Liferay Lang Builder Dependency}{section.742.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {743}Maven Plugin Builder Gradle Plugin}{2249}{chapter.743}\protected@file@percent }
\newlabel{maven-plugin-builder-gradle-plugin}{{743}{2249}{Maven Plugin Builder Gradle Plugin}{chapter.743}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.1}Usage}{2249}{section.743.1}\protected@file@percent }
\newlabel{usage-15}{{743.1}{2249}{Usage}{section.743.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.2}Tasks}{2249}{section.743.2}\protected@file@percent }
\newlabel{tasks-14}{{743.2}{2249}{Tasks}{section.743.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.3}BuildPluginDescriptorTask}{2250}{section.743.3}\protected@file@percent }
\newlabel{buildplugindescriptortask}{{743.3}{2250}{BuildPluginDescriptorTask}{section.743.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2250}{section.743.3}\protected@file@percent }
\newlabel{task-properties-14}{{743.3}{2250}{Task Properties}{section.743.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.4}Task Methods}{2251}{section.743.4}\protected@file@percent }
\newlabel{task-methods-7}{{743.4}{2251}{Task Methods}{section.743.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.5}WriteMavenSettingsTask}{2251}{section.743.5}\protected@file@percent }
\newlabel{writemavensettingstask}{{743.5}{2251}{WriteMavenSettingsTask}{section.743.5}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2251}{section.743.5}\protected@file@percent }
\newlabel{task-properties-15}{{743.5}{2251}{Task Properties}{section.743.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.6}Additional Configuration}{2252}{section.743.6}\protected@file@percent }
\newlabel{additional-configuration-8}{{743.6}{2252}{Additional Configuration}{section.743.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.7}Maven Embedder Dependency}{2252}{section.743.7}\protected@file@percent }
\newlabel{maven-embedder-dependency}{{743.7}{2252}{Maven Embedder Dependency}{section.743.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {743.8}System Properties}{2252}{section.743.8}\protected@file@percent }
\newlabel{system-properties-2}{{743.8}{2252}{System Properties}{section.743.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {744}Node Gradle Plugin}{2253}{chapter.744}\protected@file@percent }
\newlabel{node-gradle-plugin}{{744}{2253}{Node Gradle Plugin}{chapter.744}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.1}Usage}{2253}{section.744.1}\protected@file@percent }
\newlabel{usage-16}{{744.1}{2253}{Usage}{section.744.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.2}Project Extension}{2253}{section.744.2}\protected@file@percent }
\newlabel{project-extension-6}{{744.2}{2253}{Project Extension}{section.744.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.3}Tasks}{2254}{section.744.3}\protected@file@percent }
\newlabel{tasks-15}{{744.3}{2254}{Tasks}{section.744.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.4}DownloadNodeTask}{2254}{section.744.4}\protected@file@percent }
\newlabel{downloadnodetask}{{744.4}{2254}{DownloadNodeTask}{section.744.4}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2255}{section.744.4}\protected@file@percent }
\newlabel{task-properties-16}{{744.4}{2255}{Task Properties}{section.744.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.5}ExecuteNodeTask}{2255}{section.744.5}\protected@file@percent }
\newlabel{executenodetask}{{744.5}{2255}{ExecuteNodeTask}{section.744.5}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2255}{section.744.5}\protected@file@percent }
\newlabel{task-properties-17}{{744.5}{2255}{Task Properties}{section.744.5}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2255}{section.744.5}\protected@file@percent }
\newlabel{task-methods-8}{{744.5}{2255}{Task Methods}{section.744.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.6}ExecuteNodeScriptTask}{2256}{section.744.6}\protected@file@percent }
\newlabel{executenodescripttask}{{744.6}{2256}{ExecuteNodeScriptTask}{section.744.6}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2256}{section.744.6}\protected@file@percent }
\newlabel{task-properties-18}{{744.6}{2256}{Task Properties}{section.744.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.7}ExecuteNpmTask}{2256}{section.744.7}\protected@file@percent }
\newlabel{executenpmtask}{{744.7}{2256}{ExecuteNpmTask}{section.744.7}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2256}{section.744.7}\protected@file@percent }
\newlabel{task-properties-19}{{744.7}{2256}{Task Properties}{section.744.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.8}DownloadNodeModuleTask}{2257}{section.744.8}\protected@file@percent }
\newlabel{downloadnodemoduletask}{{744.8}{2257}{DownloadNodeModuleTask}{section.744.8}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2257}{section.744.8}\protected@file@percent }
\newlabel{task-properties-20}{{744.8}{2257}{Task Properties}{section.744.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.9}NpmInstallTask}{2257}{section.744.9}\protected@file@percent }
\newlabel{npminstalltask}{{744.9}{2257}{NpmInstallTask}{section.744.9}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2257}{section.744.9}\protected@file@percent }
\newlabel{task-properties-21}{{744.9}{2257}{Task Properties}{section.744.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.10}NpmShrinkwrapTask}{2258}{section.744.10}\protected@file@percent }
\newlabel{npmshrinkwraptask}{{744.10}{2258}{NpmShrinkwrapTask}{section.744.10}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2258}{section.744.10}\protected@file@percent }
\newlabel{task-properties-22}{{744.10}{2258}{Task Properties}{section.744.10}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2258}{section.744.10}\protected@file@percent }
\newlabel{task-methods-9}{{744.10}{2258}{Task Methods}{section.744.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.11}PublishNodeModuleTask}{2258}{section.744.11}\protected@file@percent }
\newlabel{publishnodemoduletask}{{744.11}{2258}{PublishNodeModuleTask}{section.744.11}{}}
\gdef \LT@lvi {\LT@entry
{1}{167.54416pt}\LT@entry
{1}{302.21085pt}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2259}{section.744.11}\protected@file@percent }
\newlabel{task-properties-23}{{744.11}{2259}{Task Properties}{section.744.11}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2259}{section.744.11}\protected@file@percent }
\newlabel{task-methods-10}{{744.11}{2259}{Task Methods}{section.744.11}{}}
\@writefile{toc}{\contentsline {section}{\numberline {744.12}npmRun\$\{script\} Task}{2259}{section.744.12}\protected@file@percent }
\newlabel{npmrunscript-task}{{744.12}{2259}{npmRun\$\{script\} Task}{section.744.12}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {745}REST Builder Gradle Plugin}{2261}{chapter.745}\protected@file@percent }
\newlabel{rest-builder-gradle-plugin}{{745}{2261}{REST Builder Gradle Plugin}{chapter.745}{}}
\@writefile{toc}{\contentsline {section}{\numberline {745.1}Usage}{2261}{section.745.1}\protected@file@percent }
\newlabel{usage-17}{{745.1}{2261}{Usage}{section.745.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {745.2}Tasks}{2261}{section.745.2}\protected@file@percent }
\newlabel{tasks-16}{{745.2}{2261}{Tasks}{section.745.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {745.3}BuildRESTTask}{2262}{section.745.3}\protected@file@percent }
\newlabel{buildresttask}{{745.3}{2262}{BuildRESTTask}{section.745.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2262}{section.745.3}\protected@file@percent }
\newlabel{task-properties-24}{{745.3}{2262}{Task Properties}{section.745.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {745.4}Additional Configuration}{2262}{section.745.4}\protected@file@percent }
\newlabel{additional-configuration-9}{{745.4}{2262}{Additional Configuration}{section.745.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {745.5}Liferay REST Builder Dependency}{2262}{section.745.5}\protected@file@percent }
\newlabel{liferay-rest-builder-dependency}{{745.5}{2262}{Liferay REST Builder Dependency}{section.745.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {746}Service Builder Gradle Plugin}{2263}{chapter.746}\protected@file@percent }
\newlabel{service-builder-gradle-plugin}{{746}{2263}{Service Builder Gradle Plugin}{chapter.746}{}}
\@writefile{toc}{\contentsline {section}{\numberline {746.1}Usage}{2263}{section.746.1}\protected@file@percent }
\newlabel{usage-18}{{746.1}{2263}{Usage}{section.746.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {746.2}Tasks}{2263}{section.746.2}\protected@file@percent }
\newlabel{tasks-17}{{746.2}{2263}{Tasks}{section.746.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {746.3}BuildServiceTask}{2265}{section.746.3}\protected@file@percent }
\newlabel{buildservicetask}{{746.3}{2265}{BuildServiceTask}{section.746.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2265}{section.746.3}\protected@file@percent }
\newlabel{task-properties-25}{{746.3}{2265}{Task Properties}{section.746.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {746.4}Additional Configuration}{2266}{section.746.4}\protected@file@percent }
\newlabel{additional-configuration-10}{{746.4}{2266}{Additional Configuration}{section.746.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {746.5}Liferay Service Builder Dependency}{2266}{section.746.5}\protected@file@percent }
\newlabel{liferay-service-builder-dependency}{{746.5}{2266}{Liferay Service Builder Dependency}{section.746.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {747}Source Formatter Gradle Plugin}{2267}{chapter.747}\protected@file@percent }
\newlabel{source-formatter-gradle-plugin}{{747}{2267}{Source Formatter Gradle Plugin}{chapter.747}{}}
\@writefile{toc}{\contentsline {section}{\numberline {747.1}Usage}{2267}{section.747.1}\protected@file@percent }
\newlabel{usage-19}{{747.1}{2267}{Usage}{section.747.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {747.2}Tasks}{2267}{section.747.2}\protected@file@percent }
\newlabel{tasks-18}{{747.2}{2267}{Tasks}{section.747.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {747.3}FormatSourceTask}{2268}{section.747.3}\protected@file@percent }
\newlabel{formatsourcetask}{{747.3}{2268}{FormatSourceTask}{section.747.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2268}{section.747.3}\protected@file@percent }
\newlabel{task-properties-26}{{747.3}{2268}{Task Properties}{section.747.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {747.4}Additional Configuration}{2269}{section.747.4}\protected@file@percent }
\newlabel{additional-configuration-11}{{747.4}{2269}{Additional Configuration}{section.747.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {747.5}Liferay Source Formatter Dependency}{2269}{section.747.5}\protected@file@percent }
\newlabel{liferay-source-formatter-dependency}{{747.5}{2269}{Liferay Source Formatter Dependency}{section.747.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {747.6}System Properties}{2269}{section.747.6}\protected@file@percent }
\newlabel{system-properties-3}{{747.6}{2269}{System Properties}{section.747.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {748}Soy Gradle Plugin}{2271}{chapter.748}\protected@file@percent }
\newlabel{soy-gradle-plugin}{{748}{2271}{Soy Gradle Plugin}{chapter.748}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.1}Usage}{2271}{section.748.1}\protected@file@percent }
\newlabel{usage-20}{{748.1}{2271}{Usage}{section.748.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.2}Soy Plugin}{2272}{section.748.2}\protected@file@percent }
\newlabel{soy-plugin}{{748.2}{2272}{Soy Plugin}{section.748.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.3}Additional Configuration}{2272}{section.748.3}\protected@file@percent }
\newlabel{additional-configuration-12}{{748.3}{2272}{Additional Configuration}{section.748.3}{}}
\@writefile{toc}{\contentsline {subsection}{Soy Dependency}{2272}{section.748.3}\protected@file@percent }
\newlabel{soy-dependency}{{748.3}{2272}{Soy Dependency}{section.748.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.4}Soy Translation Plugin}{2272}{section.748.4}\protected@file@percent }
\newlabel{soy-translation-plugin}{{748.4}{2272}{Soy Translation Plugin}{section.748.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.5}Tasks}{2273}{section.748.5}\protected@file@percent }
\newlabel{tasks-19}{{748.5}{2273}{Tasks}{section.748.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.6}BuildSoyTask}{2273}{section.748.6}\protected@file@percent }
\newlabel{buildsoytask}{{748.6}{2273}{BuildSoyTask}{section.748.6}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2273}{section.748.6}\protected@file@percent }
\newlabel{task-properties-27}{{748.6}{2273}{Task Properties}{section.748.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.7}WrapSoyAlloyTemplateTask}{2273}{section.748.7}\protected@file@percent }
\newlabel{wrapsoyalloytemplatetask}{{748.7}{2273}{WrapSoyAlloyTemplateTask}{section.748.7}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2273}{section.748.7}\protected@file@percent }
\newlabel{task-properties-28}{{748.7}{2273}{Task Properties}{section.748.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {748.8}ReplaceSoyTranslationTask}{2273}{section.748.8}\protected@file@percent }
\newlabel{replacesoytranslationtask}{{748.8}{2273}{ReplaceSoyTranslationTask}{section.748.8}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2274}{section.748.8}\protected@file@percent }
\newlabel{task-properties-29}{{748.8}{2274}{Task Properties}{section.748.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {749}Target Platform Gradle Plugin}{2275}{chapter.749}\protected@file@percent }
\newlabel{target-platform-gradle-plugin}{{749}{2275}{Target Platform Gradle Plugin}{chapter.749}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.1}Usage}{2275}{section.749.1}\protected@file@percent }
\newlabel{usage-21}{{749.1}{2275}{Usage}{section.749.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.2}Target Platform Plugin}{2276}{section.749.2}\protected@file@percent }
\newlabel{target-platform-plugin}{{749.2}{2276}{Target Platform Plugin}{section.749.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.3}Target Platform IDE Plugin}{2276}{section.749.3}\protected@file@percent }
\newlabel{target-platform-ide-plugin}{{749.3}{2276}{Target Platform IDE Plugin}{section.749.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.4}Project Extension}{2276}{section.749.4}\protected@file@percent }
\newlabel{project-extension-7}{{749.4}{2276}{Project Extension}{section.749.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.5}Tasks}{2277}{section.749.5}\protected@file@percent }
\newlabel{tasks-20}{{749.5}{2277}{Tasks}{section.749.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.6}ResolveTask}{2277}{section.749.6}\protected@file@percent }
\newlabel{resolvetask}{{749.6}{2277}{ResolveTask}{section.749.6}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2277}{section.749.6}\protected@file@percent }
\newlabel{task-properties-30}{{749.6}{2277}{Task Properties}{section.749.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.7}Additional Configuration}{2278}{section.749.7}\protected@file@percent }
\newlabel{additional-configuration-13}{{749.7}{2278}{Additional Configuration}{section.749.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.8}Target Platform BOMs Dependency}{2278}{section.749.8}\protected@file@percent }
\newlabel{target-platform-boms-dependency}{{749.8}{2278}{Target Platform BOMs Dependency}{section.749.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.9}Target Platform Bundles Dependency}{2278}{section.749.9}\protected@file@percent }
\newlabel{target-platform-bundles-dependency}{{749.9}{2278}{Target Platform Bundles Dependency}{section.749.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.10}Target Platform Distro Dependency}{2278}{section.749.10}\protected@file@percent }
\newlabel{target-platform-distro-dependency}{{749.10}{2278}{Target Platform Distro Dependency}{section.749.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {749.11}Target Platform Requirements Dependency}{2279}{section.749.11}\protected@file@percent }
\newlabel{target-platform-requirements-dependency}{{749.11}{2279}{Target Platform Requirements Dependency}{section.749.11}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {750}Theme Builder Gradle Plugin}{2281}{chapter.750}\protected@file@percent }
\newlabel{theme-builder-gradle-plugin}{{750}{2281}{Theme Builder Gradle Plugin}{chapter.750}{}}
\@writefile{toc}{\contentsline {section}{\numberline {750.1}Usage}{2281}{section.750.1}\protected@file@percent }
\newlabel{usage-22}{{750.1}{2281}{Usage}{section.750.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {750.2}Tasks}{2282}{section.750.2}\protected@file@percent }
\newlabel{tasks-21}{{750.2}{2282}{Tasks}{section.750.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {750.3}BuildThemeTask}{2282}{section.750.3}\protected@file@percent }
\newlabel{buildthemetask}{{750.3}{2282}{BuildThemeTask}{section.750.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2282}{section.750.3}\protected@file@percent }
\newlabel{task-properties-31}{{750.3}{2282}{Task Properties}{section.750.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {750.4}Additional Configuration}{2283}{section.750.4}\protected@file@percent }
\newlabel{additional-configuration-14}{{750.4}{2283}{Additional Configuration}{section.750.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {750.5}Liferay Theme Builder Dependency}{2283}{section.750.5}\protected@file@percent }
\newlabel{liferay-theme-builder-dependency}{{750.5}{2283}{Liferay Theme Builder Dependency}{section.750.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {750.6}Parent Theme Dependencies}{2283}{section.750.6}\protected@file@percent }
\newlabel{parent-theme-dependencies}{{750.6}{2283}{Parent Theme Dependencies}{section.750.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {751}TLD Formatter Gradle Plugin}{2285}{chapter.751}\protected@file@percent }
\newlabel{tld-formatter-gradle-plugin}{{751}{2285}{TLD Formatter Gradle Plugin}{chapter.751}{}}
\@writefile{toc}{\contentsline {section}{\numberline {751.1}Usage}{2285}{section.751.1}\protected@file@percent }
\newlabel{usage-23}{{751.1}{2285}{Usage}{section.751.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {751.2}Tasks}{2285}{section.751.2}\protected@file@percent }
\newlabel{tasks-22}{{751.2}{2285}{Tasks}{section.751.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {751.3}FormatTLDTask}{2286}{section.751.3}\protected@file@percent }
\newlabel{formattldtask}{{751.3}{2286}{FormatTLDTask}{section.751.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2286}{section.751.3}\protected@file@percent }
\newlabel{task-properties-32}{{751.3}{2286}{Task Properties}{section.751.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {751.4}Additional Configuration}{2286}{section.751.4}\protected@file@percent }
\newlabel{additional-configuration-15}{{751.4}{2286}{Additional Configuration}{section.751.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {751.5}Liferay TLD Formatter Dependency}{2286}{section.751.5}\protected@file@percent }
\newlabel{liferay-tld-formatter-dependency}{{751.5}{2286}{Liferay TLD Formatter Dependency}{section.751.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {752}TLDDoc Builder Gradle Plugin}{2287}{chapter.752}\protected@file@percent }
\newlabel{tlddoc-builder-gradle-plugin}{{752}{2287}{TLDDoc Builder Gradle Plugin}{chapter.752}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.1}Usage}{2287}{section.752.1}\protected@file@percent }
\newlabel{usage-24}{{752.1}{2287}{Usage}{section.752.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.2}TLDDoc Builder Plugin}{2288}{section.752.2}\protected@file@percent }
\newlabel{tlddoc-builder-plugin}{{752.2}{2288}{TLDDoc Builder Plugin}{section.752.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.3}App TLDDoc Builder Plugin}{2288}{section.752.3}\protected@file@percent }
\newlabel{app-tlddoc-builder-plugin}{{752.3}{2288}{App TLDDoc Builder Plugin}{section.752.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.4}Project Extension}{2289}{section.752.4}\protected@file@percent }
\newlabel{project-extension-8}{{752.4}{2289}{Project Extension}{section.752.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.5}Tasks}{2289}{section.752.5}\protected@file@percent }
\newlabel{tasks-23}{{752.5}{2289}{Tasks}{section.752.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.6}TLDDocTask}{2289}{section.752.6}\protected@file@percent }
\newlabel{tlddoctask}{{752.6}{2289}{TLDDocTask}{section.752.6}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2289}{section.752.6}\protected@file@percent }
\newlabel{task-properties-33}{{752.6}{2289}{Task Properties}{section.752.6}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2289}{section.752.6}\protected@file@percent }
\newlabel{task-methods-11}{{752.6}{2289}{Task Methods}{section.752.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.7}ValidateSchemaTask}{2290}{section.752.7}\protected@file@percent }
\newlabel{validateschematask}{{752.7}{2290}{ValidateSchemaTask}{section.752.7}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2290}{section.752.7}\protected@file@percent }
\newlabel{task-properties-34}{{752.7}{2290}{Task Properties}{section.752.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.8}Additional Configuration}{2290}{section.752.8}\protected@file@percent }
\newlabel{additional-configuration-16}{{752.8}{2290}{Additional Configuration}{section.752.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {752.9}Tag Library Documentation Generator Dependency}{2290}{section.752.9}\protected@file@percent }
\newlabel{tag-library-documentation-generator-dependency}{{752.9}{2290}{Tag Library Documentation Generator Dependency}{section.752.9}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {753}Whip Gradle Plugin}{2291}{chapter.753}\protected@file@percent }
\newlabel{whip-gradle-plugin}{{753}{2291}{Whip Gradle Plugin}{chapter.753}{}}
\@writefile{toc}{\contentsline {section}{\numberline {753.1}Usage}{2291}{section.753.1}\protected@file@percent }
\newlabel{usage-25}{{753.1}{2291}{Usage}{section.753.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {753.2}Project Extension}{2292}{section.753.2}\protected@file@percent }
\newlabel{project-extension-9}{{753.2}{2292}{Project Extension}{section.753.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {753.3}Task Extension}{2292}{section.753.3}\protected@file@percent }
\newlabel{task-extension}{{753.3}{2292}{Task Extension}{section.753.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {753.4}Additional Configuration}{2292}{section.753.4}\protected@file@percent }
\newlabel{additional-configuration-17}{{753.4}{2292}{Additional Configuration}{section.753.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {753.5}Liferay Whip Dependency}{2292}{section.753.5}\protected@file@percent }
\newlabel{liferay-whip-dependency}{{753.5}{2292}{Liferay Whip Dependency}{section.753.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {754}WSDD Builder Gradle Plugin}{2293}{chapter.754}\protected@file@percent }
\newlabel{wsdd-builder-gradle-plugin}{{754}{2293}{WSDD Builder Gradle Plugin}{chapter.754}{}}
\@writefile{toc}{\contentsline {section}{\numberline {754.1}Usage}{2293}{section.754.1}\protected@file@percent }
\newlabel{usage-26}{{754.1}{2293}{Usage}{section.754.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {754.2}Tasks}{2293}{section.754.2}\protected@file@percent }
\newlabel{tasks-24}{{754.2}{2293}{Tasks}{section.754.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {754.3}BuildWSDDTask}{2294}{section.754.3}\protected@file@percent }
\newlabel{buildwsddtask}{{754.3}{2294}{BuildWSDDTask}{section.754.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2294}{section.754.3}\protected@file@percent }
\newlabel{task-properties-35}{{754.3}{2294}{Task Properties}{section.754.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {754.4}Additional Configuration}{2294}{section.754.4}\protected@file@percent }
\newlabel{additional-configuration-18}{{754.4}{2294}{Additional Configuration}{section.754.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {754.5}Liferay WSDD Builder Dependency}{2294}{section.754.5}\protected@file@percent }
\newlabel{liferay-wsdd-builder-dependency}{{754.5}{2294}{Liferay WSDD Builder Dependency}{section.754.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {755}WSDL Builder Gradle Plugin}{2297}{chapter.755}\protected@file@percent }
\newlabel{wsdl-builder-gradle-plugin}{{755}{2297}{WSDL Builder Gradle Plugin}{chapter.755}{}}
\@writefile{toc}{\contentsline {section}{\numberline {755.1}Usage}{2297}{section.755.1}\protected@file@percent }
\newlabel{usage-27}{{755.1}{2297}{Usage}{section.755.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {755.2}Tasks}{2297}{section.755.2}\protected@file@percent }
\newlabel{tasks-25}{{755.2}{2297}{Tasks}{section.755.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {755.3}BuildWSDLTask}{2298}{section.755.3}\protected@file@percent }
\newlabel{buildwsdltask}{{755.3}{2298}{BuildWSDLTask}{section.755.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2298}{section.755.3}\protected@file@percent }
\newlabel{task-properties-36}{{755.3}{2298}{Task Properties}{section.755.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Methods}{2298}{section.755.3}\protected@file@percent }
\newlabel{task-methods-12}{{755.3}{2298}{Task Methods}{section.755.3}{}}
\@writefile{toc}{\contentsline {subsection}{Helper Tasks}{2298}{section.755.3}\protected@file@percent }
\newlabel{helper-tasks-1}{{755.3}{2298}{Helper Tasks}{section.755.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {755.4}Additional Configuration}{2299}{section.755.4}\protected@file@percent }
\newlabel{additional-configuration-19}{{755.4}{2299}{Additional Configuration}{section.755.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {755.5}Apache Axis Dependency}{2299}{section.755.5}\protected@file@percent }
\newlabel{apache-axis-dependency}{{755.5}{2299}{Apache Axis Dependency}{section.755.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {756}XML Formatter Gradle Plugin}{2301}{chapter.756}\protected@file@percent }
\newlabel{xml-formatter-gradle-plugin}{{756}{2301}{XML Formatter Gradle Plugin}{chapter.756}{}}
\@writefile{toc}{\contentsline {section}{\numberline {756.1}Usage}{2301}{section.756.1}\protected@file@percent }
\newlabel{usage-28}{{756.1}{2301}{Usage}{section.756.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {756.2}Tasks}{2301}{section.756.2}\protected@file@percent }
\newlabel{tasks-26}{{756.2}{2301}{Tasks}{section.756.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {756.3}FormatXMLTask}{2302}{section.756.3}\protected@file@percent }
\newlabel{formatxmltask}{{756.3}{2302}{FormatXMLTask}{section.756.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2302}{section.756.3}\protected@file@percent }
\newlabel{task-properties-37}{{756.3}{2302}{Task Properties}{section.756.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {756.4}Additional Configuration}{2302}{section.756.4}\protected@file@percent }
\newlabel{additional-configuration-20}{{756.4}{2302}{Additional Configuration}{section.756.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {756.5}Liferay XML Formatter Dependency}{2302}{section.756.5}\protected@file@percent }
\newlabel{liferay-xml-formatter-dependency}{{756.5}{2302}{Liferay XML Formatter Dependency}{section.756.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {757}XSD Builder Gradle Plugin}{2303}{chapter.757}\protected@file@percent }
\newlabel{xsd-builder-gradle-plugin}{{757}{2303}{XSD Builder Gradle Plugin}{chapter.757}{}}
\@writefile{toc}{\contentsline {section}{\numberline {757.1}Usage}{2303}{section.757.1}\protected@file@percent }
\newlabel{usage-29}{{757.1}{2303}{Usage}{section.757.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {757.2}Tasks}{2303}{section.757.2}\protected@file@percent }
\newlabel{tasks-27}{{757.2}{2303}{Tasks}{section.757.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {757.3}BuildXSDTask}{2304}{section.757.3}\protected@file@percent }
\newlabel{buildxsdtask}{{757.3}{2304}{BuildXSDTask}{section.757.3}{}}
\@writefile{toc}{\contentsline {subsection}{Task Properties}{2304}{section.757.3}\protected@file@percent }
\newlabel{task-properties-38}{{757.3}{2304}{Task Properties}{section.757.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {757.4}Additional Configuration}{2304}{section.757.4}\protected@file@percent }
\newlabel{additional-configuration-21}{{757.4}{2304}{Additional Configuration}{section.757.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {757.5}Apache XMLBeans Dependency}{2304}{section.757.5}\protected@file@percent }
\newlabel{apache-xmlbeans-dependency}{{757.5}{2304}{Apache XMLBeans Dependency}{section.757.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {758}Liferay Faces}{2305}{chapter.758}\protected@file@percent }
\newlabel{liferay-faces}{{758}{2305}{Liferay Faces}{chapter.758}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {759}Liferay Faces Version Scheme}{2307}{chapter.759}\protected@file@percent }
\newlabel{liferay-faces-version-scheme}{{759}{2307}{Liferay Faces Version Scheme}{chapter.759}{}}
\@writefile{toc}{\contentsline {section}{\numberline {759.1}Using The Liferay Faces Archetype Portlet}{2307}{section.759.1}\protected@file@percent }
\newlabel{using-the-liferay-faces-archetype-portlet}{{759.1}{2307}{Using The Liferay Faces Archetype Portlet}{section.759.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {759.2}Liferay Faces Alloy}{2307}{section.759.2}\protected@file@percent }
\newlabel{liferay-faces-alloy}{{759.2}{2307}{Liferay Faces Alloy}{section.759.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {759.3}Liferay Faces Bridge}{2307}{section.759.3}\protected@file@percent }
\newlabel{liferay-faces-bridge}{{759.3}{2307}{Liferay Faces Bridge}{section.759.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {759.4}Liferay Faces Bridge Ext}{2308}{section.759.4}\protected@file@percent }
\newlabel{liferay-faces-bridge-ext}{{759.4}{2308}{Liferay Faces Bridge Ext}{section.759.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {759.5}Liferay Faces Portal}{2308}{section.759.5}\protected@file@percent }
\newlabel{liferay-faces-portal}{{759.5}{2308}{Liferay Faces Portal}{section.759.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {759.6}Liferay Faces Util}{2308}{section.759.6}\protected@file@percent }
\newlabel{liferay-faces-util}{{759.6}{2308}{Liferay Faces Util}{section.759.6}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {759.1}{\ignorespaces The Liferay Faces dependency diagram helps visualize how components interact and depend on each other.}}{2309}{figure.759.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {760}Understanding Liferay Faces Bridge}{2311}{chapter.760}\protected@file@percent }
\newlabel{understanding-liferay-faces-bridge}{{760}{2311}{Understanding Liferay Faces Bridge}{chapter.760}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {760.1}{\ignorespaces The different phases of the JSF Lifecycle are executed depending on which phase of the Portlet lifecycle is being executed.}}{2312}{figure.760.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {760.1}Related Topics}{2313}{section.760.1}\protected@file@percent }
\newlabel{related-topics-45}{{760.1}{2313}{Related Topics}{section.760.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {761}Understanding Liferay Faces Alloy}{2315}{chapter.761}\protected@file@percent }
\newlabel{understanding-liferay-faces-alloy}{{761}{2315}{Understanding Liferay Faces Alloy}{chapter.761}{}}
\@writefile{toc}{\contentsline {section}{\numberline {761.1}Related Topics}{2315}{section.761.1}\protected@file@percent }
\newlabel{related-topics-46}{{761.1}{2315}{Related Topics}{section.761.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {762}Understanding Liferay Faces Portal}{2317}{chapter.762}\protected@file@percent }
\newlabel{understanding-liferay-faces-portal}{{762}{2317}{Understanding Liferay Faces Portal}{chapter.762}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {762.1}{\ignorespaces The required \texttt {.jar} files are downloaded for your JSF portlet based on the JSF UI Component Suite you configured.}}{2317}{figure.762.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {762.1}Related Topics}{2318}{section.762.1}\protected@file@percent }
\newlabel{related-topics-47}{{762.1}{2318}{Related Topics}{section.762.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {763}Maven Plugins}{2319}{chapter.763}\protected@file@percent }
\newlabel{maven-plugins}{{763}{2319}{Maven Plugins}{chapter.763}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {764}Bundle Support Plugin}{2321}{chapter.764}\protected@file@percent }
\newlabel{bundle-support-plugin}{{764}{2321}{Bundle Support Plugin}{chapter.764}{}}
\@writefile{toc}{\contentsline {section}{\numberline {764.1}Usage}{2321}{section.764.1}\protected@file@percent }
\newlabel{usage-30}{{764.1}{2321}{Usage}{section.764.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {764.2}Goals}{2322}{section.764.2}\protected@file@percent }
\newlabel{goals}{{764.2}{2322}{Goals}{section.764.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {764.3}clean Goal's Available Parameters}{2322}{section.764.3}\protected@file@percent }
\newlabel{clean-goals-available-parameters}{{764.3}{2322}{clean Goal's Available Parameters}{section.764.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {764.4}create-token Goal's Available Parameters}{2322}{section.764.4}\protected@file@percent }
\newlabel{create-token-goals-available-parameters}{{764.4}{2322}{create-token Goal's Available Parameters}{section.764.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {764.5}deploy Goal's Available Parameters}{2323}{section.764.5}\protected@file@percent }
\newlabel{deploy-goals-available-parameters}{{764.5}{2323}{deploy Goal's Available Parameters}{section.764.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {764.6}dist Goal's Available Parameters}{2323}{section.764.6}\protected@file@percent }
\newlabel{dist-goals-available-parameters}{{764.6}{2323}{dist Goal's Available Parameters}{section.764.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {764.7}init Goal's Available Parameters}{2323}{section.764.7}\protected@file@percent }
\newlabel{init-goals-available-parameters}{{764.7}{2323}{init Goal's Available Parameters}{section.764.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {765}CSS Builder Plugin}{2325}{chapter.765}\protected@file@percent }
\newlabel{css-builder-plugin}{{765}{2325}{CSS Builder Plugin}{chapter.765}{}}
\@writefile{toc}{\contentsline {section}{\numberline {765.1}Usage}{2325}{section.765.1}\protected@file@percent }
\newlabel{usage-31}{{765.1}{2325}{Usage}{section.765.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {765.2}Goals}{2325}{section.765.2}\protected@file@percent }
\newlabel{goals-1}{{765.2}{2325}{Goals}{section.765.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {765.3}Available Parameters}{2326}{section.765.3}\protected@file@percent }
\newlabel{available-parameters}{{765.3}{2326}{Available Parameters}{section.765.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {766}DB Support Plugin}{2327}{chapter.766}\protected@file@percent }
\newlabel{db-support-plugin}{{766}{2327}{DB Support Plugin}{chapter.766}{}}
\@writefile{toc}{\contentsline {section}{\numberline {766.1}Usage}{2327}{section.766.1}\protected@file@percent }
\newlabel{usage-32}{{766.1}{2327}{Usage}{section.766.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {766.2}Goals}{2327}{section.766.2}\protected@file@percent }
\newlabel{goals-2}{{766.2}{2327}{Goals}{section.766.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {766.3}Available Parameters}{2328}{section.766.3}\protected@file@percent }
\newlabel{available-parameters-1}{{766.3}{2328}{Available Parameters}{section.766.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {767}Deployment Helper Plugin}{2329}{chapter.767}\protected@file@percent }
\newlabel{deployment-helper-plugin}{{767}{2329}{Deployment Helper Plugin}{chapter.767}{}}
\@writefile{toc}{\contentsline {section}{\numberline {767.1}Usage}{2329}{section.767.1}\protected@file@percent }
\newlabel{usage-33}{{767.1}{2329}{Usage}{section.767.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {767.2}Goals}{2329}{section.767.2}\protected@file@percent }
\newlabel{goals-3}{{767.2}{2329}{Goals}{section.767.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {767.3}Available Parameters}{2329}{section.767.3}\protected@file@percent }
\newlabel{available-parameters-2}{{767.3}{2329}{Available Parameters}{section.767.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {768}Javadoc Formatter Plugin}{2331}{chapter.768}\protected@file@percent }
\newlabel{javadoc-formatter-plugin}{{768}{2331}{Javadoc Formatter Plugin}{chapter.768}{}}
\@writefile{toc}{\contentsline {section}{\numberline {768.1}Usage}{2331}{section.768.1}\protected@file@percent }
\newlabel{usage-34}{{768.1}{2331}{Usage}{section.768.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {768.2}Goals}{2331}{section.768.2}\protected@file@percent }
\newlabel{goals-4}{{768.2}{2331}{Goals}{section.768.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {768.3}Available Parameters}{2332}{section.768.3}\protected@file@percent }
\newlabel{available-parameters-3}{{768.3}{2332}{Available Parameters}{section.768.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {769}Lang Builder Plugin}{2333}{chapter.769}\protected@file@percent }
\newlabel{lang-builder-plugin}{{769}{2333}{Lang Builder Plugin}{chapter.769}{}}
\@writefile{toc}{\contentsline {section}{\numberline {769.1}Usage}{2333}{section.769.1}\protected@file@percent }
\newlabel{usage-35}{{769.1}{2333}{Usage}{section.769.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {769.2}Goals}{2333}{section.769.2}\protected@file@percent }
\newlabel{goals-5}{{769.2}{2333}{Goals}{section.769.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {769.3}Available Parameters}{2333}{section.769.3}\protected@file@percent }
\newlabel{available-parameters-4}{{769.3}{2333}{Available Parameters}{section.769.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {770}REST Builder Plugin}{2335}{chapter.770}\protected@file@percent }
\newlabel{rest-builder-plugin}{{770}{2335}{REST Builder Plugin}{chapter.770}{}}
\@writefile{toc}{\contentsline {section}{\numberline {770.1}Usage}{2335}{section.770.1}\protected@file@percent }
\newlabel{usage-36}{{770.1}{2335}{Usage}{section.770.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {770.2}Goals}{2335}{section.770.2}\protected@file@percent }
\newlabel{goals-6}{{770.2}{2335}{Goals}{section.770.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {770.3}Available Parameters}{2335}{section.770.3}\protected@file@percent }
\newlabel{available-parameters-5}{{770.3}{2335}{Available Parameters}{section.770.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {771}Service Builder Plugin}{2337}{chapter.771}\protected@file@percent }
\newlabel{service-builder-plugin}{{771}{2337}{Service Builder Plugin}{chapter.771}{}}
\@writefile{toc}{\contentsline {section}{\numberline {771.1}Usage}{2337}{section.771.1}\protected@file@percent }
\newlabel{usage-37}{{771.1}{2337}{Usage}{section.771.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {771.2}Goals}{2337}{section.771.2}\protected@file@percent }
\newlabel{goals-7}{{771.2}{2337}{Goals}{section.771.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {771.3}Available Parameters}{2337}{section.771.3}\protected@file@percent }
\newlabel{available-parameters-6}{{771.3}{2337}{Available Parameters}{section.771.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {772}Source Formatter Plugin}{2339}{chapter.772}\protected@file@percent }
\newlabel{source-formatter-plugin}{{772}{2339}{Source Formatter Plugin}{chapter.772}{}}
\@writefile{toc}{\contentsline {section}{\numberline {772.1}Usage}{2339}{section.772.1}\protected@file@percent }
\newlabel{usage-38}{{772.1}{2339}{Usage}{section.772.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {772.2}Goals}{2339}{section.772.2}\protected@file@percent }
\newlabel{goals-8}{{772.2}{2339}{Goals}{section.772.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {772.3}Available Parameters}{2340}{section.772.3}\protected@file@percent }
\newlabel{available-parameters-7}{{772.3}{2340}{Available Parameters}{section.772.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {773}Theme Builder Plugin}{2341}{chapter.773}\protected@file@percent }
\newlabel{theme-builder-plugin}{{773}{2341}{Theme Builder Plugin}{chapter.773}{}}
\@writefile{toc}{\contentsline {section}{\numberline {773.1}Usage}{2341}{section.773.1}\protected@file@percent }
\newlabel{usage-39}{{773.1}{2341}{Usage}{section.773.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {773.2}Goals}{2341}{section.773.2}\protected@file@percent }
\newlabel{goals-9}{{773.2}{2341}{Goals}{section.773.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {773.3}Available Parameters}{2342}{section.773.3}\protected@file@percent }
\newlabel{available-parameters-8}{{773.3}{2342}{Available Parameters}{section.773.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {774}TLD Formatter Plugin}{2343}{chapter.774}\protected@file@percent }
\newlabel{tld-formatter-plugin}{{774}{2343}{TLD Formatter Plugin}{chapter.774}{}}
\@writefile{toc}{\contentsline {section}{\numberline {774.1}Usage}{2343}{section.774.1}\protected@file@percent }
\newlabel{usage-40}{{774.1}{2343}{Usage}{section.774.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {774.2}Goals}{2343}{section.774.2}\protected@file@percent }
\newlabel{goals-10}{{774.2}{2343}{Goals}{section.774.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {774.3}Available Parameters}{2343}{section.774.3}\protected@file@percent }
\newlabel{available-parameters-9}{{774.3}{2343}{Available Parameters}{section.774.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {775}WSDD Builder Plugin}{2345}{chapter.775}\protected@file@percent }
\newlabel{wsdd-builder-plugin}{{775}{2345}{WSDD Builder Plugin}{chapter.775}{}}
\@writefile{toc}{\contentsline {section}{\numberline {775.1}Usage}{2345}{section.775.1}\protected@file@percent }
\newlabel{usage-41}{{775.1}{2345}{Usage}{section.775.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {775.2}Goals}{2345}{section.775.2}\protected@file@percent }
\newlabel{goals-11}{{775.2}{2345}{Goals}{section.775.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {775.3}Available Parameters}{2345}{section.775.3}\protected@file@percent }
\newlabel{available-parameters-10}{{775.3}{2345}{Available Parameters}{section.775.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {776}XML Formatter Plugin}{2347}{chapter.776}\protected@file@percent }
\newlabel{xml-formatter-plugin}{{776}{2347}{XML Formatter Plugin}{chapter.776}{}}
\@writefile{toc}{\contentsline {section}{\numberline {776.1}Usage}{2347}{section.776.1}\protected@file@percent }
\newlabel{usage-42}{{776.1}{2347}{Usage}{section.776.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {776.2}Goals}{2347}{section.776.2}\protected@file@percent }
\newlabel{goals-12}{{776.2}{2347}{Goals}{section.776.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {776.3}Available Parameters}{2347}{section.776.3}\protected@file@percent }
\newlabel{available-parameters-11}{{776.3}{2347}{Available Parameters}{section.776.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {777}PortletMVC4Spring}{2349}{chapter.777}\protected@file@percent }
\newlabel{portletmvc4spring}{{777}{2349}{PortletMVC4Spring}{chapter.777}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {778}PortletMVC4Spring Project Anatomy}{2351}{chapter.778}\protected@file@percent }
\newlabel{portletmvc4spring-project-anatomy}{{778}{2351}{PortletMVC4Spring Project Anatomy}{chapter.778}{}}
\@writefile{toc}{\contentsline {section}{\numberline {778.1}Maven Commands for Generating PortletMVC4Spring Projects}{2351}{section.778.1}\protected@file@percent }
\newlabel{maven-commands-for-generating-portletmvc4spring-projects}{{778.1}{2351}{Maven Commands for Generating PortletMVC4Spring Projects}{section.778.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {778.2}SP/JSPX Form Portlet}{2351}{section.778.2}\protected@file@percent }
\newlabel{spjspx-form-portlet}{{778.2}{2351}{SP/JSPX Form Portlet}{section.778.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {778.3}Thymeleaf Form Portlet}{2351}{section.778.3}\protected@file@percent }
\newlabel{thymeleaf-form-portlet}{{778.3}{2351}{Thymeleaf Form Portlet}{section.778.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {778.4}Project Structure}{2352}{section.778.4}\protected@file@percent }
\newlabel{project-structure}{{778.4}{2352}{Project Structure}{section.778.4}{}}
\gdef \LT@lvii {\LT@entry
{1}{270.08083pt}\LT@entry
{1}{199.67418pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {779}PortletMVC4Spring Annotations}{2353}{chapter.779}\protected@file@percent }
\newlabel{portletmvc4spring-annotations}{{779}{2353}{PortletMVC4Spring Annotations}{chapter.779}{}}
\@writefile{toc}{\contentsline {section}{\numberline {779.1}\texttt {@RenderMapping} Annotation Examples}{2353}{section.779.1}\protected@file@percent }
\newlabel{rendermapping-annotation-examples}{{779.1}{2353}{\texorpdfstring {\texttt {@RenderMapping} Annotation Examples}{@RenderMapping Annotation Examples}}{section.779.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {779.2}\texttt {@ActionMapping} Annotation Examples}{2353}{section.779.2}\protected@file@percent }
\newlabel{actionmapping-annotation-examples}{{779.2}{2353}{\texorpdfstring {\texttt {@ActionMapping} Annotation Examples}{@ActionMapping Annotation Examples}}{section.779.2}{}}
\gdef \LT@lviii {\LT@entry
{1}{270.08083pt}\LT@entry
{1}{199.67418pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {780}PortletMVC4Spring Configuration Files}{2355}{chapter.780}\protected@file@percent }
\newlabel{portletmvc4spring-configuration-files}{{780}{2355}{PortletMVC4Spring Configuration Files}{chapter.780}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.1}web.xml}{2355}{section.780.1}\protected@file@percent }
\newlabel{web.xml}{{780.1}{2355}{web.xml}{section.780.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.2}portlet.xml}{2357}{section.780.2}\protected@file@percent }
\newlabel{portlet.xml}{{780.2}{2357}{portlet.xml}{section.780.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.3}liferay-portlet.xml}{2358}{section.780.3}\protected@file@percent }
\newlabel{liferay-portlet.xml}{{780.3}{2358}{liferay-portlet.xml}{section.780.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.4}liferay-display.xml}{2359}{section.780.4}\protected@file@percent }
\newlabel{liferay-display.xml}{{780.4}{2359}{liferay-display.xml}{section.780.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.5}Portlet Application Context}{2359}{section.780.5}\protected@file@percent }
\newlabel{portlet-application-context}{{780.5}{2359}{Portlet Application Context}{section.780.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.6}Portlet Contexts}{2360}{section.780.6}\protected@file@percent }
\newlabel{portlet-contexts}{{780.6}{2360}{Portlet Contexts}{section.780.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.7}liferay-plugin-package.properties}{2361}{section.780.7}\protected@file@percent }
\newlabel{liferay-plugin-package.properties-1}{{780.7}{2361}{liferay-plugin-package.properties}{section.780.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {780.8}Related Topics}{2361}{section.780.8}\protected@file@percent }
\newlabel{related-topics-48}{{780.8}{2361}{Related Topics}{section.780.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {781}Project Templates}{2363}{chapter.781}\protected@file@percent }
\newlabel{project-templates}{{781}{2363}{Project Templates}{chapter.781}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {782}Activator Template}{2365}{chapter.782}\protected@file@percent }
\newlabel{activator-template}{{782}{2365}{Activator Template}{chapter.782}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {783}API Template}{2367}{chapter.783}\protected@file@percent }
\newlabel{api-template}{{783}{2367}{API Template}{chapter.783}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {784}Control Menu Entry Template}{2369}{chapter.784}\protected@file@percent }
\newlabel{control-menu-entry-template}{{784}{2369}{Control Menu Entry Template}{chapter.784}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {785}Form Field Template}{2371}{chapter.785}\protected@file@percent }
\newlabel{form-field-template}{{785}{2371}{Form Field Template}{chapter.785}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {786}Fragment Template}{2373}{chapter.786}\protected@file@percent }
\newlabel{fragment-template}{{786}{2373}{Fragment Template}{chapter.786}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {787}FreeMarker Portlet Template}{2375}{chapter.787}\protected@file@percent }
\newlabel{freemarker-portlet-template}{{787}{2375}{FreeMarker Portlet Template}{chapter.787}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {788}Layout Template}{2377}{chapter.788}\protected@file@percent }
\newlabel{layout-template}{{788}{2377}{Layout Template}{chapter.788}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {789}Modules Ext Template}{2379}{chapter.789}\protected@file@percent }
\newlabel{modules-ext-template}{{789}{2379}{Modules Ext Template}{chapter.789}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {790}MVC Portlet Template}{2381}{chapter.790}\protected@file@percent }
\newlabel{mvc-portlet-template}{{790}{2381}{MVC Portlet Template}{chapter.790}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {791}Panel App Template}{2383}{chapter.791}\protected@file@percent }
\newlabel{panel-app-template}{{791}{2383}{Panel App Template}{chapter.791}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {792}Portlet Configuration Icon}{2385}{chapter.792}\protected@file@percent }
\newlabel{portlet-configuration-icon}{{792}{2385}{Portlet Configuration Icon}{chapter.792}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {793}Portlet Provider Template}{2387}{chapter.793}\protected@file@percent }
\newlabel{portlet-provider-template}{{793}{2387}{Portlet Provider Template}{chapter.793}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {794}Portlet Toolbar Contributor Template}{2389}{chapter.794}\protected@file@percent }
\newlabel{portlet-toolbar-contributor-template}{{794}{2389}{Portlet Toolbar Contributor Template}{chapter.794}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {795}REST Template}{2391}{chapter.795}\protected@file@percent }
\newlabel{rest-template}{{795}{2391}{REST Template}{chapter.795}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {796}Service Builder Template}{2393}{chapter.796}\protected@file@percent }
\newlabel{service-builder-template}{{796}{2393}{Service Builder Template}{chapter.796}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {797}Service Template}{2395}{chapter.797}\protected@file@percent }
\newlabel{service-template}{{797}{2395}{Service Template}{chapter.797}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {798}Service Wrapper Template}{2397}{chapter.798}\protected@file@percent }
\newlabel{service-wrapper-template}{{798}{2397}{Service Wrapper Template}{chapter.798}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {799}Simulation Panel Entry Template}{2399}{chapter.799}\protected@file@percent }
\newlabel{simulation-panel-entry-template}{{799}{2399}{Simulation Panel Entry Template}{chapter.799}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {800}Social Bookmark Template}{2401}{chapter.800}\protected@file@percent }
\newlabel{social-bookmark-template}{{800}{2401}{Social Bookmark Template}{chapter.800}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {800.1}{\ignorespaces Click the magnifying glass icon to search the current URL using Google Search.}}{2402}{figure.800.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {801}Spring MVC Portlet Template}{2403}{chapter.801}\protected@file@percent }
\newlabel{spring-mvc-portlet-template}{{801}{2403}{Spring MVC Portlet Template}{chapter.801}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {802}Template Context Contributor Template}{2407}{chapter.802}\protected@file@percent }
\newlabel{template-context-contributor-template}{{802}{2407}{Template Context Contributor Template}{chapter.802}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {803}Theme Contributor Template}{2409}{chapter.803}\protected@file@percent }
\newlabel{theme-contributor-template}{{803}{2409}{Theme Contributor Template}{chapter.803}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {804}Theme Template}{2411}{chapter.804}\protected@file@percent }
\newlabel{theme-template}{{804}{2411}{Theme Template}{chapter.804}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {805}WAR Core Ext}{2413}{chapter.805}\protected@file@percent }
\newlabel{war-core-ext}{{805}{2413}{WAR Core Ext}{chapter.805}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {806}WAR Hook Template}{2417}{chapter.806}\protected@file@percent }
\newlabel{war-hook-template}{{806}{2417}{WAR Hook Template}{chapter.806}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {807}WAR MVC Portlet Template}{2419}{chapter.807}\protected@file@percent }
\newlabel{war-mvc-portlet-template}{{807}{2419}{WAR MVC Portlet Template}{chapter.807}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {808}Sample Projects}{2421}{chapter.808}\protected@file@percent }
\newlabel{sample-projects}{{808}{2421}{Sample Projects}{chapter.808}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {809}Apps}{2423}{chapter.809}\protected@file@percent }
\newlabel{apps}{{809}{2423}{Apps}{chapter.809}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {810}Service Builder Samples}{2425}{chapter.810}\protected@file@percent }
\newlabel{service-builder-samples}{{810}{2425}{Service Builder Samples}{chapter.810}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {811}Service Builder Application Demonstrating Actionable Dynamic Query}{2427}{chapter.811}\protected@file@percent }
\newlabel{service-builder-application-demonstrating-actionable-dynamic-query}{{811}{2427}{Service Builder Application Demonstrating Actionable Dynamic Query}{chapter.811}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {811.1}{\ignorespaces This sample provides options to add entities and perform a mass update.}}{2427}{figure.811.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {811.1}What API(s) and/or code components does this sample highlight?}{2428}{section.811.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight}{{811.1}{2428}{What API(s) and/or code components does this sample highlight?}{section.811.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {811.2}How does this sample leverage the API(s) and/or code component?}{2428}{section.811.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component}{{811.2}{2428}{How does this sample leverage the API(s) and/or code component?}{section.811.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {812}Service Builder Application Using External Database via JDBC}{2429}{chapter.812}\protected@file@percent }
\newlabel{service-builder-application-using-external-database-via-jdbc}{{812}{2429}{Service Builder Application Using External Database via JDBC}{chapter.812}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {812.1}{\ignorespaces This sample prints out the values previously inputted into the database.}}{2430}{figure.812.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {812.1}What API(s) and/or code components does this sample highlight?}{2431}{section.812.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-1}{{812.1}{2431}{What API(s) and/or code components does this sample highlight?}{section.812.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {812.2}How does this sample leverage the API(s) and/or code component?}{2431}{section.812.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-1}{{812.2}{2431}{How does this sample leverage the API(s) and/or code component?}{section.812.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {812.3}Configuring the Data Source}{2431}{section.812.3}\protected@file@percent }
\newlabel{configuring-the-data-source}{{812.3}{2431}{Configuring the Data Source}{section.812.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {812.4}Accessing Data}{2431}{section.812.4}\protected@file@percent }
\newlabel{accessing-data}{{812.4}{2431}{Accessing Data}{section.812.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {813}Service Builder Application Using External Database via JNDI}{2433}{chapter.813}\protected@file@percent }
\newlabel{service-builder-application-using-external-database-via-jndi}{{813}{2433}{Service Builder Application Using External Database via JNDI}{chapter.813}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {813.1}{\ignorespaces This sample prints out the values previously inputted into the database.}}{2435}{figure.813.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {813.1}What API(s) and/or code components does this sample highlight?}{2436}{section.813.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-2}{{813.1}{2436}{What API(s) and/or code components does this sample highlight?}{section.813.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {813.2}How does this sample leverage the API(s) and/or code component?}{2436}{section.813.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-2}{{813.2}{2436}{How does this sample leverage the API(s) and/or code component?}{section.813.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {813.3}Additional Information}{2436}{section.813.3}\protected@file@percent }
\newlabel{additional-information}{{813.3}{2436}{Additional Information}{section.813.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {814}Workflow Samples}{2437}{chapter.814}\protected@file@percent }
\newlabel{workflow-samples}{{814}{2437}{Workflow Samples}{chapter.814}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {815}Workflow Asset Application}{2439}{chapter.815}\protected@file@percent }
\newlabel{workflow-asset-application}{{815}{2439}{Workflow Asset Application}{chapter.815}{}}
\@writefile{toc}{\contentsline {section}{\numberline {815.1}What API(s) and/or code components does this sample highlight?}{2439}{section.815.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-3}{{815.1}{2439}{What API(s) and/or code components does this sample highlight?}{section.815.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {815.2}How does this sample leverage the API(s) and/or code component?}{2439}{section.815.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-3}{{815.2}{2439}{How does this sample leverage the API(s) and/or code component?}{section.815.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {816}Workflow Application}{2441}{chapter.816}\protected@file@percent }
\newlabel{workflow-application}{{816}{2441}{Workflow Application}{chapter.816}{}}
\@writefile{toc}{\contentsline {section}{\numberline {816.1}What API(s) and/or code components does this sample highlight?}{2441}{section.816.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-4}{{816.1}{2441}{What API(s) and/or code components does this sample highlight?}{section.816.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {816.2}How does this sample leverage the API(s) and/or code component?}{2441}{section.816.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-4}{{816.2}{2441}{How does this sample leverage the API(s) and/or code component?}{section.816.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {817}Greedy Policy Option Application}{2443}{chapter.817}\protected@file@percent }
\newlabel{greedy-policy-option-application}{{817}{2443}{Greedy Policy Option Application}{chapter.817}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {817.1}{\ignorespaces The Greedy Policy Option app provides two portlets that only print text. You'll dive deeper later to discover their interesting capabilities involving services.}}{2443}{figure.817.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {817.1}What API(s) and/or code components does this sample highlight?}{2443}{section.817.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-5}{{817.1}{2443}{What API(s) and/or code components does this sample highlight?}{section.817.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {817.2}How does this sample leverage the API(s) and/or code component?}{2444}{section.817.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-5}{{817.2}{2444}{How does this sample leverage the API(s) and/or code component?}{section.817.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {817.3}Binding a newly deployed component's service reference to the highest ranking service instance that's available initially}{2445}{section.817.3}\protected@file@percent }
\newlabel{binding-a-newly-deployed-components-service-reference-to-the-highest-ranking-service-instance-thats-available-initially}{{817.3}{2445}{Binding a newly deployed component's service reference to the highest ranking service instance that's available initially}{section.817.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {817.2}{\ignorespaces \emph {Reluctant Portlet} displays the message ``SomeService says I am Default!''}}{2446}{figure.817.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {817.3}{\ignorespaces \emph {Greedy Portlet} displays the message ``SomeService says I am better, use me!''}}{2447}{figure.817.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {817.4}Deploying a module with a higher ranked service instance for binding to greedy references immediately}{2447}{section.817.4}\protected@file@percent }
\newlabel{deploying-a-module-with-a-higher-ranked-service-instance-for-binding-to-greedy-references-immediately}{{817.4}{2447}{Deploying a module with a higher ranked service instance for binding to greedy references immediately}{section.817.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {817.4}{\ignorespaces The \emph {Greedy Portlet} is using a \texttt {HigherRankedService} instance}}{2447}{figure.817.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {817.5}Configuring a component to reference a different service instance dynamically}{2447}{section.817.5}\protected@file@percent }
\newlabel{configuring-a-component-to-reference-a-different-service-instance-dynamically}{{817.5}{2447}{Configuring a component to reference a different service instance dynamically}{section.817.5}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {817.5}{\ignorespaces \emph {Reluctant Portlet} is using the \texttt {HigherRankedService} instance instead of a \texttt {DefaultService} instance.}}{2448}{figure.817.5}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {817.6}Where Is This Sample?}{2448}{section.817.6}\protected@file@percent }
\newlabel{where-is-this-sample}{{817.6}{2448}{Where Is This Sample?}{section.817.6}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {818}Kotlin Portlet}{2449}{chapter.818}\protected@file@percent }
\newlabel{kotlin-portlet}{{818}{2449}{Kotlin Portlet}{chapter.818}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {818.1}{\ignorespaces After saving the inputted name, it's displayed as a greeting on the portlet page.}}{2449}{figure.818.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {818.1}What API(s) and/or code components does this sample highlight?}{2449}{section.818.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-6}{{818.1}{2449}{What API(s) and/or code components does this sample highlight?}{section.818.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {818.2}How does this sample leverage the API(s) and/or code component?}{2449}{section.818.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-6}{{818.2}{2449}{How does this sample leverage the API(s) and/or code component?}{section.818.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {818.3}Where Is This Sample?}{2450}{section.818.3}\protected@file@percent }
\newlabel{where-is-this-sample-1}{{818.3}{2450}{Where Is This Sample?}{section.818.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {819}Shared Language Keys}{2451}{chapter.819}\protected@file@percent }
\newlabel{shared-language-keys}{{819}{2451}{Shared Language Keys}{chapter.819}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {819.1}{\ignorespaces The sample JSP portlet displays three language keys.}}{2451}{figure.819.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {819.1}What API(s) and/or code components does this sample highlight?}{2451}{section.819.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-7}{{819.1}{2451}{What API(s) and/or code components does this sample highlight?}{section.819.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {819.2}How does this sample leverage the API(s) and/or code component?}{2452}{section.819.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-7}{{819.2}{2452}{How does this sample leverage the API(s) and/or code component?}{section.819.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {819.2}{\ignorespaces The Language Web portlet displays three phrases, two of which are shared from a different module.}}{2452}{figure.819.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {819.3}Where Is This Sample?}{2453}{section.819.3}\protected@file@percent }
\newlabel{where-is-this-sample-2}{{819.3}{2453}{Where Is This Sample?}{section.819.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {820}Simulation Panel App}{2455}{chapter.820}\protected@file@percent }
\newlabel{simulation-panel-app}{{820}{2455}{Simulation Panel App}{chapter.820}{}}
\@writefile{toc}{\contentsline {section}{\numberline {820.1}What API(s) and/or code components does this sample highlight?}{2455}{section.820.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-8}{{820.1}{2455}{What API(s) and/or code components does this sample highlight?}{section.820.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {820.2}How does this sample leverage the API(s) and/or code component?}{2455}{section.820.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-8}{{820.2}{2455}{How does this sample leverage the API(s) and/or code component?}{section.820.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {820.3}Where Is This Sample?}{2456}{section.820.3}\protected@file@percent }
\newlabel{where-is-this-sample-3}{{820.3}{2456}{Where Is This Sample?}{section.820.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {821}Extensions}{2457}{chapter.821}\protected@file@percent }
\newlabel{extensions}{{821}{2457}{Extensions}{chapter.821}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {822}Control Menu Entry}{2459}{chapter.822}\protected@file@percent }
\newlabel{control-menu-entry}{{822}{2459}{Control Menu Entry}{chapter.822}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {822.1}{\ignorespaces The User area of the Control Menu is provided an additional link button when the Control Menu Entry sample is deployed to Liferay DXP.}}{2459}{figure.822.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {822.1}What API(s) and/or code components does this sample highlight?}{2459}{section.822.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-9}{{822.1}{2459}{What API(s) and/or code components does this sample highlight?}{section.822.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {822.2}How does this sample leverage the API(s) and/or code component?}{2459}{section.822.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-9}{{822.2}{2459}{How does this sample leverage the API(s) and/or code component?}{section.822.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {822.3}Where Is This Sample?}{2460}{section.822.3}\protected@file@percent }
\newlabel{where-is-this-sample-4}{{822.3}{2460}{Where Is This Sample?}{section.822.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {823}Document Action}{2461}{chapter.823}\protected@file@percent }
\newlabel{document-action}{{823}{2461}{Document Action}{chapter.823}{}}
\@writefile{toc}{\contentsline {section}{\numberline {823.1}What API(s) and/or code components does this sample highlight?}{2461}{section.823.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-10}{{823.1}{2461}{What API(s) and/or code components does this sample highlight?}{section.823.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {823.2}How does this sample leverage the API(s) and/or code component?}{2461}{section.823.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-10}{{823.2}{2461}{How does this sample leverage the API(s) and/or code component?}{section.823.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {823.1}{\ignorespaces The new \emph {Blade Basic Info} option is available from the entry's Options menu.}}{2462}{figure.823.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {823.3}Where Is This Sample?}{2463}{section.823.3}\protected@file@percent }
\newlabel{where-is-this-sample-5}{{823.3}{2463}{Where Is This Sample?}{section.823.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {824}Gogo Shell Command}{2465}{chapter.824}\protected@file@percent }
\newlabel{gogo-shell-command}{{824}{2465}{Gogo Shell Command}{chapter.824}{}}
\@writefile{toc}{\contentsline {section}{\numberline {824.1}What API(s) and/or code components does this sample highlight?}{2465}{section.824.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-11}{{824.1}{2465}{What API(s) and/or code components does this sample highlight?}{section.824.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {824.2}How does this sample leverage the API(s) and/or code component?}{2465}{section.824.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-11}{{824.2}{2465}{How does this sample leverage the API(s) and/or code component?}{section.824.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {824.1}{\ignorespaces The sample Gogo shell command is listed with all the available commands.}}{2466}{figure.824.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {824.2}{\ignorespaces The outcome of executing the \texttt {usercount} command.}}{2466}{figure.824.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {824.3}Where Is This Sample?}{2467}{section.824.3}\protected@file@percent }
\newlabel{where-is-this-sample-6}{{824.3}{2467}{Where Is This Sample?}{section.824.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {825}Index Settings Contributor}{2469}{chapter.825}\protected@file@percent }
\newlabel{index-settings-contributor}{{825}{2469}{Index Settings Contributor}{chapter.825}{}}
\@writefile{toc}{\contentsline {section}{\numberline {825.1}What API(s) and/or code components does this sample highlight?}{2469}{section.825.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-12}{{825.1}{2469}{What API(s) and/or code components does this sample highlight?}{section.825.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {825.1}{\ignorespaces This sample added four new index properties.}}{2470}{figure.825.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {825.2}How does this sample leverage the API(s) and/or code component?}{2470}{section.825.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-12}{{825.2}{2470}{How does this sample leverage the API(s) and/or code component?}{section.825.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {825.3}Where Is This Sample?}{2471}{section.825.3}\protected@file@percent }
\newlabel{where-is-this-sample-7}{{825.3}{2471}{Where Is This Sample?}{section.825.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {826}Indexer Post Processor}{2473}{chapter.826}\protected@file@percent }
\newlabel{indexer-post-processor}{{826}{2473}{Indexer Post Processor}{chapter.826}{}}
\@writefile{toc}{\contentsline {section}{\numberline {826.1}What API(s) and/or code components does this sample highlight?}{2473}{section.826.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-13}{{826.1}{2473}{What API(s) and/or code components does this sample highlight?}{section.826.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {826.2}How does this sample leverage the API(s) and/or code component?}{2473}{section.826.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-13}{{826.2}{2473}{How does this sample leverage the API(s) and/or code component?}{section.826.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {826.3}Where Is This Sample?}{2474}{section.826.3}\protected@file@percent }
\newlabel{where-is-this-sample-8}{{826.3}{2474}{Where Is This Sample?}{section.826.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {827}Model Listener}{2475}{chapter.827}\protected@file@percent }
\newlabel{model-listener}{{827}{2475}{Model Listener}{chapter.827}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {827.1}{\ignorespaces The sample model listener's message in the console.}}{2475}{figure.827.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {827.1}What API(s) and/or code components does this sample highlight?}{2475}{section.827.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-14}{{827.1}{2475}{What API(s) and/or code components does this sample highlight?}{section.827.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {827.2}How does this sample leverage the API(s) and/or code component?}{2475}{section.827.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-14}{{827.2}{2475}{How does this sample leverage the API(s) and/or code component?}{section.827.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {827.2}{\ignorespaces The page's HTML title updated by the model listener sample.}}{2476}{figure.827.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {827.3}Where Is This Sample?}{2477}{section.827.3}\protected@file@percent }
\newlabel{where-is-this-sample-9}{{827.3}{2477}{Where Is This Sample?}{section.827.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {828}Screen Name Validator}{2479}{chapter.828}\protected@file@percent }
\newlabel{screen-name-validator}{{828}{2479}{Screen Name Validator}{chapter.828}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {828.1}{\ignorespaces Enter reserved words for the screen name validator.}}{2479}{figure.828.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {828.2}{\ignorespaces The error message displays when inputting a reserved word for the screen name.}}{2480}{figure.828.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {828.1}What API(s) and/or code components does this sample highlight?}{2480}{section.828.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-15}{{828.1}{2480}{What API(s) and/or code components does this sample highlight?}{section.828.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {828.2}How does this sample leverage the API(s) and/or code component?}{2480}{section.828.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-15}{{828.2}{2480}{How does this sample leverage the API(s) and/or code component?}{section.828.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {828.3}Where Is This Sample?}{2480}{section.828.3}\protected@file@percent }
\newlabel{where-is-this-sample-10}{{828.3}{2480}{Where Is This Sample?}{section.828.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {829}Servlet}{2481}{chapter.829}\protected@file@percent }
\newlabel{servlet}{{829}{2481}{Servlet}{chapter.829}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {829.1}{\ignorespaces The servlet displays \emph {Hello World} from the configured servlet page URL.}}{2481}{figure.829.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {829.2}{\ignorespaces The servlet also logs info in the console.}}{2481}{figure.829.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {829.1}What API(s) and/or code components does this sample highlight?}{2482}{section.829.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-16}{{829.1}{2482}{What API(s) and/or code components does this sample highlight?}{section.829.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {829.2}How does this sample leverage the API(s) and/or code component?}{2482}{section.829.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-16}{{829.2}{2482}{How does this sample leverage the API(s) and/or code component?}{section.829.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {829.3}Where Is This Sample?}{2482}{section.829.3}\protected@file@percent }
\newlabel{where-is-this-sample-11}{{829.3}{2482}{Where Is This Sample?}{section.829.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {830}Overrides}{2483}{chapter.830}\protected@file@percent }
\newlabel{overrides}{{830}{2483}{Overrides}{chapter.830}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {831}Module JSP Override}{2485}{chapter.831}\protected@file@percent }
\newlabel{module-jsp-override}{{831}{2485}{Module JSP Override}{chapter.831}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {831.1}{\ignorespaces The customized Sign In form with the new \emph {changed} text.}}{2485}{figure.831.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {831.1}What API(s) and/or code components does this sample highlight?}{2486}{section.831.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-17}{{831.1}{2486}{What API(s) and/or code components does this sample highlight?}{section.831.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {831.2}How does this sample leverage the API(s) and/or code component?}{2486}{section.831.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-17}{{831.2}{2486}{How does this sample leverage the API(s) and/or code component?}{section.831.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {831.3}Where Is This Sample?}{2486}{section.831.3}\protected@file@percent }
\newlabel{where-is-this-sample-12}{{831.3}{2486}{Where Is This Sample?}{section.831.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {832}Resource Bundle Override}{2487}{chapter.832}\protected@file@percent }
\newlabel{resource-bundle-override}{{832}{2487}{Resource Bundle Override}{chapter.832}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {832.1}{\ignorespaces The customized Login portlet displays the new language key.}}{2487}{figure.832.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {832.1}What API(s) and/or code components does this sample highlight?}{2487}{section.832.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-18}{{832.1}{2487}{What API(s) and/or code components does this sample highlight?}{section.832.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {832.2}How does this sample leverage the API(s) and/or code component?}{2487}{section.832.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-18}{{832.2}{2487}{How does this sample leverage the API(s) and/or code component?}{section.832.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {832.3}Where Is This Sample?}{2488}{section.832.3}\protected@file@percent }
\newlabel{where-is-this-sample-13}{{832.3}{2488}{Where Is This Sample?}{section.832.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {833}Themes}{2489}{chapter.833}\protected@file@percent }
\newlabel{themes}{{833}{2489}{Themes}{chapter.833}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {834}Simple Theme}{2491}{chapter.834}\protected@file@percent }
\newlabel{simple-theme}{{834}{2491}{Simple Theme}{chapter.834}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {834.1}{\ignorespaces A theme based off of the Styled base theme is created when the Theme Blade sample is deployed to Liferay Portal.}}{2491}{figure.834.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {834.1}What API(s) and/or code components does this sample highlight?}{2491}{section.834.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-19}{{834.1}{2491}{What API(s) and/or code components does this sample highlight?}{section.834.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {834.2}How does this sample leverage the API(s) and/or code component?}{2492}{section.834.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-19}{{834.2}{2492}{How does this sample leverage the API(s) and/or code component?}{section.834.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {834.3}Where Is This Sample?}{2492}{section.834.3}\protected@file@percent }
\newlabel{where-is-this-sample-14}{{834.3}{2492}{Where Is This Sample?}{section.834.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {835}Template Context Contributor}{2493}{chapter.835}\protected@file@percent }
\newlabel{template-context-contributor}{{835}{2493}{Template Context Contributor}{chapter.835}{}}
\@writefile{toc}{\contentsline {section}{\numberline {835.1}What API(s) and/or code components does this sample highlight?}{2493}{section.835.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-20}{{835.1}{2493}{What API(s) and/or code components does this sample highlight?}{section.835.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {835.2}How does this sample leverage the API(s) and/or code component?}{2493}{section.835.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-20}{{835.2}{2493}{How does this sample leverage the API(s) and/or code component?}{section.835.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {835.3}Where Is This Sample?}{2494}{section.835.3}\protected@file@percent }
\newlabel{where-is-this-sample-15}{{835.3}{2494}{Where Is This Sample?}{section.835.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {836}Theme Contributor}{2495}{chapter.836}\protected@file@percent }
\newlabel{theme-contributor}{{836}{2495}{Theme Contributor}{chapter.836}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {836.1}{\ignorespaces Your Liferay DXP pages and menu fonts now have a yellow tint.}}{2495}{figure.836.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {836.2}{\ignorespaces The message is printed to your browser's console window using JavaScript.}}{2495}{figure.836.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {836.1}What API(s) and/or code components does this sample highlight?}{2496}{section.836.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-21}{{836.1}{2496}{What API(s) and/or code components does this sample highlight?}{section.836.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {836.2}How does this sample leverage the API(s) and/or code component?}{2496}{section.836.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-21}{{836.2}{2496}{How does this sample leverage the API(s) and/or code component?}{section.836.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {836.3}Where Is This Sample?}{2496}{section.836.3}\protected@file@percent }
\newlabel{where-is-this-sample-16}{{836.3}{2496}{Where Is This Sample?}{section.836.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {837}Ext}{2497}{chapter.837}\protected@file@percent }
\newlabel{ext}{{837}{2497}{Ext}{chapter.837}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {838}Login Web Ext}{2499}{chapter.838}\protected@file@percent }
\newlabel{login-web-ext}{{838}{2499}{Login Web Ext}{chapter.838}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {838.1}{\ignorespaces The Login Ext module customizes the original Login module.}}{2499}{figure.838.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {838.1}What API(s) and/or code components does this sample highlight?}{2500}{section.838.1}\protected@file@percent }
\newlabel{what-apis-andor-code-components-does-this-sample-highlight-22}{{838.1}{2500}{What API(s) and/or code components does this sample highlight?}{section.838.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {838.2}How does this sample leverage the API(s) and/or code component?}{2500}{section.838.2}\protected@file@percent }
\newlabel{how-does-this-sample-leverage-the-apis-andor-code-component-22}{{838.2}{2500}{How does this sample leverage the API(s) and/or code component?}{section.838.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {838.3}Where Is This Sample?}{2501}{section.838.3}\protected@file@percent }
\newlabel{where-is-this-sample-17}{{838.3}{2501}{Where Is This Sample?}{section.838.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {839}Segmentation and Personalization Reference}{2503}{chapter.839}\protected@file@percent }
\newlabel{segmentation-and-personalization-reference}{{839}{2503}{Segmentation and Personalization Reference}{chapter.839}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {840}Defining Segmentation Criteria}{2505}{chapter.840}\protected@file@percent }
\newlabel{defining-segmentation-criteria}{{840}{2505}{Defining Segmentation Criteria}{chapter.840}{}}
\@writefile{toc}{\contentsline {section}{\numberline {840.1}User Properties}{2506}{section.840.1}\protected@file@percent }
\newlabel{user-properties}{{840.1}{2506}{User Properties}{section.840.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {840.2}Organization Properties}{2506}{section.840.2}\protected@file@percent }
\newlabel{organization-properties}{{840.2}{2506}{Organization Properties}{section.840.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {840.3}Session Properties}{2506}{section.840.3}\protected@file@percent }
\newlabel{session-properties}{{840.3}{2506}{Session Properties}{section.840.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {841}Tooling}{2507}{chapter.841}\protected@file@percent }
\newlabel{tooling}{{841}{2507}{Tooling}{chapter.841}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {842}Creating a Project}{2509}{chapter.842}\protected@file@percent }
\newlabel{creating-a-project}{{842}{2509}{Creating a Project}{chapter.842}{}}
\@writefile{toc}{\contentsline {section}{\numberline {842.1}Blade CLI}{2509}{section.842.1}\protected@file@percent }
\newlabel{blade-cli}{{842.1}{2509}{Blade CLI}{section.842.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {842.2}Liferay Dev Studio}{2510}{section.842.2}\protected@file@percent }
\newlabel{liferay-dev-studio}{{842.2}{2510}{Liferay Dev Studio}{section.842.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {842.1}{\ignorespaces The New Liferay Module Project wizard offers project templates for JAR and WAR-based projects.}}{2510}{figure.842.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {842.2}{\ignorespaces Specify your component class's details in the Portlet Component Class Wizard.}}{2511}{figure.842.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {842.3}Liferay IntelliJ Plugin}{2511}{section.842.3}\protected@file@percent }
\newlabel{liferay-intellij-plugin}{{842.3}{2511}{Liferay IntelliJ Plugin}{section.842.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {842.3}{\ignorespaces Selecting \emph {Liferay Module} opens the New Liferay Modules wizard.}}{2511}{figure.842.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {842.4}{\ignorespaces Choose the project template to create your module.}}{2512}{figure.842.4}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {842.4}Maven}{2512}{section.842.4}\protected@file@percent }
\newlabel{maven}{{842.4}{2512}{Maven}{section.842.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {843}Deploying a Project}{2513}{chapter.843}\protected@file@percent }
\newlabel{deploying-a-project}{{843}{2513}{Deploying a Project}{chapter.843}{}}
\@writefile{toc}{\contentsline {section}{\numberline {843.1}Blade CLI}{2513}{section.843.1}\protected@file@percent }
\newlabel{blade-cli-1}{{843.1}{2513}{Blade CLI}{section.843.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {843.2}Gradle}{2514}{section.843.2}\protected@file@percent }
\newlabel{gradle}{{843.2}{2514}{Gradle}{section.843.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {843.3}Liferay Dev Studio}{2514}{section.843.3}\protected@file@percent }
\newlabel{liferay-dev-studio-1}{{843.3}{2514}{Liferay Dev Studio}{section.843.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {843.4}Liferay IntelliJ Plugin}{2514}{section.843.4}\protected@file@percent }
\newlabel{liferay-intellij-plugin-1}{{843.4}{2514}{Liferay IntelliJ Plugin}{section.843.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {843.1}{\ignorespaces Using the this deployment method is convenient when deploying multiple projects.}}{2515}{figure.843.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {843.2}{\ignorespaces Verify that your project built successfully.}}{2515}{figure.843.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {843.5}Maven}{2516}{section.843.5}\protected@file@percent }
\newlabel{maven-1}{{843.5}{2516}{Maven}{section.843.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {844}Blade CLI}{2517}{chapter.844}\protected@file@percent }
\newlabel{blade-cli-2}{{844}{2517}{Blade CLI}{chapter.844}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {845}Installing Blade CLI}{2519}{chapter.845}\protected@file@percent }
\newlabel{installing-blade-cli}{{845}{2519}{Installing Blade CLI}{chapter.845}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {845.1}{\ignorespaces Determine where your Liferay Workspace should reside, if you want one.}}{2520}{figure.845.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {845.2}{\ignorespaces Select the product version you'll use with your Liferay Workspace.}}{2520}{figure.845.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {846}Installing Blade CLI with Proxy Requirements}{2521}{chapter.846}\protected@file@percent }
\newlabel{installing-blade-cli-with-proxy-requirements}{{846}{2521}{Installing Blade CLI with Proxy Requirements}{chapter.846}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {847}Managing Your Liferay Server with Blade CLI}{2523}{chapter.847}\protected@file@percent }
\newlabel{managing-your-liferay-server-with-blade-cli}{{847}{2523}{Managing Your Liferay Server with Blade CLI}{chapter.847}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {847.1}{\ignorespaces Blade CLI accesses the Gogo shell script to run the \texttt {lb} command.}}{2524}{figure.847.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {848}Generating Project Samples with Blade CLI}{2527}{chapter.848}\protected@file@percent }
\newlabel{generating-project-samples-with-blade-cli}{{848}{2527}{Generating Project Samples with Blade CLI}{chapter.848}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {849}Updating Blade CLI}{2529}{chapter.849}\protected@file@percent }
\newlabel{updating-blade-cli}{{849}{2529}{Updating Blade CLI}{chapter.849}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {850}Converting Plugins SDK Projects with Blade CLI}{2531}{chapter.850}\protected@file@percent }
\newlabel{converting-plugins-sdk-projects-with-blade-cli}{{850}{2531}{Converting Plugins SDK Projects with Blade CLI}{chapter.850}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {851}Extending Blade CLI}{2533}{chapter.851}\protected@file@percent }
\newlabel{extending-blade-cli}{{851}{2533}{Extending Blade CLI}{chapter.851}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {852}Creating Custom Commands for Blade CLI}{2535}{chapter.852}\protected@file@percent }
\newlabel{creating-custom-commands-for-blade-cli}{{852}{2535}{Creating Custom Commands for Blade CLI}{chapter.852}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {853}Creating Custom Project Templates for Blade CLI}{2537}{chapter.853}\protected@file@percent }
\newlabel{creating-custom-project-templates-for-blade-cli}{{853}{2537}{Creating Custom Project Templates for Blade CLI}{chapter.853}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {854}Installing New Extensions for Blade CLI}{2539}{chapter.854}\protected@file@percent }
\newlabel{installing-new-extensions-for-blade-cli}{{854}{2539}{Installing New Extensions for Blade CLI}{chapter.854}{}}
\@writefile{toc}{\contentsline {section}{\numberline {854.1}Installing a New Extension}{2539}{section.854.1}\protected@file@percent }
\newlabel{installing-a-new-extension}{{854.1}{2539}{Installing a New Extension}{section.854.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {854.2}Uninstalling an Extension}{2539}{section.854.2}\protected@file@percent }
\newlabel{uninstalling-an-extension}{{854.2}{2539}{Uninstalling an Extension}{section.854.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {855}Creating a Blade Profile}{2541}{chapter.855}\protected@file@percent }
\newlabel{creating-a-blade-profile}{{855}{2541}{Creating a Blade Profile}{chapter.855}{}}
\@writefile{toc}{\contentsline {section}{\numberline {855.1}Creating a New Profile}{2541}{section.855.1}\protected@file@percent }
\newlabel{creating-a-new-profile}{{855.1}{2541}{Creating a New Profile}{section.855.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {855.2}Setting a Profile}{2542}{section.855.2}\protected@file@percent }
\newlabel{setting-a-profile}{{855.2}{2542}{Setting a Profile}{section.855.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {856}Common Errors with Blade CLI}{2545}{chapter.856}\protected@file@percent }
\newlabel{common-errors-with-blade-cli}{{856}{2545}{Common Errors with Blade CLI}{chapter.856}{}}
\@writefile{toc}{\contentsline {section}{\numberline {856.1}The blade command is not available in my CLI}{2545}{section.856.1}\protected@file@percent }
\newlabel{the-blade-command-is-not-available-in-my-cli}{{856.1}{2545}{The blade command is not available in my CLI}{section.856.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {856.2}I can't update my Blade CLI version}{2546}{section.856.2}\protected@file@percent }
\newlabel{i-cant-update-my-blade-cli-version}{{856.2}{2546}{I can't update my Blade CLI version}{section.856.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {857}Liferay Dev Studio}{2547}{chapter.857}\protected@file@percent }
\newlabel{liferay-dev-studio-2}{{857}{2547}{Liferay Dev Studio}{chapter.857}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {858}Installing Liferay Dev Studio}{2549}{chapter.858}\protected@file@percent }
\newlabel{installing-liferay-dev-studio}{{858}{2549}{Installing Liferay Dev Studio}{chapter.858}{}}
\@writefile{toc}{\contentsline {section}{\numberline {858.1}Install the Dev Studio Bundle}{2549}{section.858.1}\protected@file@percent }
\newlabel{install-the-dev-studio-bundle}{{858.1}{2549}{Install the Dev Studio Bundle}{section.858.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {858.2}Install Dev Studio into Eclipse}{2550}{section.858.2}\protected@file@percent }
\newlabel{install-dev-studio-into-eclipse}{{858.2}{2550}{Install Dev Studio into Eclipse}{section.858.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {858.3}Install Dev Studio into Eclipse from a ZIP File}{2550}{section.858.3}\protected@file@percent }
\newlabel{install-dev-studio-into-eclipse-from-a-zip-file}{{858.3}{2550}{Install Dev Studio into Eclipse from a ZIP File}{section.858.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {859}Setting Proxy Requirements for Dev Studio}{2551}{chapter.859}\protected@file@percent }
\newlabel{setting-proxy-requirements-for-dev-studio}{{859}{2551}{Setting Proxy Requirements for Dev Studio}{chapter.859}{}}
\@writefile{toc}{\contentsline {section}{\numberline {859.1}Additional Proxy Settings}{2551}{section.859.1}\protected@file@percent }
\newlabel{additional-proxy-settings}{{859.1}{2551}{Additional Proxy Settings}{section.859.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {859.1}{\ignorespaces You can configure your proxy settings in Dev Studio's Network Connections menu.}}{2552}{figure.859.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {860}Installing a Liferay Server in Dev Studio}{2553}{chapter.860}\protected@file@percent }
\newlabel{installing-a-liferay-server-in-dev-studio}{{860}{2553}{Installing a Liferay Server in Dev Studio}{chapter.860}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {860.1}{\ignorespaces Choose the type of server you want to create.}}{2554}{figure.860.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {860.2}{\ignorespaces Specify the installation folder of the bundle.}}{2555}{figure.860.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {860.3}{\ignorespaces Your new server appears under the \emph {Servers} view.}}{2555}{figure.860.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {861}Importing Projects in Dev Studio}{2557}{chapter.861}\protected@file@percent }
\newlabel{importing-projects-in-dev-studio}{{861}{2557}{Importing Projects in Dev Studio}{chapter.861}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {861.1}{\ignorespaces You can import a single project or folder of projects.}}{2557}{figure.861.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {862}Using the Gogo Shell in Dev Studio}{2559}{chapter.862}\protected@file@percent }
\newlabel{using-the-gogo-shell-in-dev-studio}{{862}{2559}{Using the Gogo Shell in Dev Studio}{chapter.862}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {862.1}{\ignorespaces Select \emph {Open Gogo Shell} to open a terminal window in Dev Studio using Gogo shell.}}{2559}{figure.862.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {862.2}{\ignorespaces You can check to see if your project deployed successfully to Liferay using the Gogo shell.}}{2560}{figure.862.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {863}Searching Liferay DXP Source in Dev Studio}{2561}{chapter.863}\protected@file@percent }
\newlabel{searching-liferay-dxp-source-in-dev-studio}{{863}{2561}{Searching Liferay DXP Source in Dev Studio}{chapter.863}{}}
\@writefile{toc}{\contentsline {section}{\numberline {863.1}Search Class Hierarchy}{2561}{section.863.1}\protected@file@percent }
\newlabel{search-class-hierarchy}{{863.1}{2561}{Search Class Hierarchy}{section.863.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {863.1}{\ignorespaces Browse the Type Hierarchy window and open the provided classes for examples on how to extend a class.}}{2562}{figure.863.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {863.2}Search Method Declarations}{2562}{section.863.2}\protected@file@percent }
\newlabel{search-method-declarations}{{863.2}{2562}{Search Method Declarations}{section.863.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {863.2}{\ignorespaces All declarations of the method are returned in the Search window.}}{2563}{figure.863.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {863.3}Search Annotation References}{2563}{section.863.3}\protected@file@percent }
\newlabel{search-annotation-references}{{863.3}{2563}{Search Annotation References}{section.863.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {863.3}{\ignorespaces All matching annotations are displayed in the Search window.}}{2564}{figure.863.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {864}Debugging Liferay DXP Source in Dev Studio}{2565}{chapter.864}\protected@file@percent }
\newlabel{debugging-liferay-dxp-source-in-dev-studio}{{864}{2565}{Debugging Liferay DXP Source in Dev Studio}{chapter.864}{}}
\@writefile{toc}{\contentsline {section}{\numberline {864.1}Configure Your Target Platform}{2565}{section.864.1}\protected@file@percent }
\newlabel{configure-your-target-platform}{{864.1}{2565}{Configure Your Target Platform}{section.864.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {864.2}Configure a Liferay Server and Start It in Debug Mode}{2566}{section.864.2}\protected@file@percent }
\newlabel{configure-a-liferay-server-and-start-it-in-debug-mode}{{864.2}{2566}{Configure a Liferay Server and Start It in Debug Mode}{section.864.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {864.1}{\ignorespaces The red box in this screenshot highlights the debug button. Click this button to start the server in debug mode.}}{2566}{figure.864.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {865}Updating Liferay Dev Studio}{2567}{chapter.865}\protected@file@percent }
\newlabel{updating-liferay-dev-studio}{{865}{2567}{Updating Liferay Dev Studio}{chapter.865}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {865.1}{\ignorespaces Make sure to check all the Dev Studio components you wish to install.}}{2567}{figure.865.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {866}Gradle in Dev Studio}{2569}{chapter.866}\protected@file@percent }
\newlabel{gradle-in-dev-studio}{{866}{2569}{Gradle in Dev Studio}{chapter.866}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {866.1}{\ignorespaces Navigate to \emph {Help} → \emph {Installation Details} to view plugins included in Dev Studio.}}{2569}{figure.866.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {866.1}Creating Pure Gradle Projects}{2570}{section.866.1}\protected@file@percent }
\newlabel{creating-pure-gradle-projects}{{866.1}{2570}{Creating Pure Gradle Projects}{section.866.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {866.2}Importing Pure Gradle Projects}{2570}{section.866.2}\protected@file@percent }
\newlabel{importing-pure-gradle-projects}{{866.2}{2570}{Importing Pure Gradle Projects}{section.866.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {866.3}Gradle Tasks and Executions}{2570}{section.866.3}\protected@file@percent }
\newlabel{gradle-tasks-and-executions}{{866.3}{2570}{Gradle Tasks and Executions}{section.866.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {866.2}{\ignorespaces You can specify your Gradle distribution and advanced options such as home directories, JVM options, and program arguments.}}{2571}{figure.866.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {866.3}{\ignorespaces You can specify what Gradle project to import from the \emph {Import Gradle Project} wizard.}}{2572}{figure.866.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {866.4}{\ignorespaces Navigate into your preferred Gradle project to view its available Gradle tasks.}}{2572}{figure.866.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {866.5}{\ignorespaces The Gradle Executions view helps you visualize the Gradle build process.}}{2573}{figure.866.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {866.6}{\ignorespaces Make sure to always refresh your Gradle project in Dev Studio after build script edits.}}{2573}{figure.866.6}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {867}Maven in Dev Studio}{2575}{chapter.867}\protected@file@percent }
\newlabel{maven-in-dev-studio}{{867}{2575}{Maven in Dev Studio}{chapter.867}{}}
\@writefile{toc}{\contentsline {section}{\numberline {867.1}Installing Maven Plugins for Dev Studio}{2575}{section.867.1}\protected@file@percent }
\newlabel{installing-maven-plugins-for-dev-studio}{{867.1}{2575}{Installing Maven Plugins for Dev Studio}{section.867.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {867.1}{\ignorespaces You can install all the necessary Maven plugins for Dev Studio by installing the \emph {Liferay IDE Maven Support} option.}}{2576}{figure.867.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {867.2}Importing Maven Projects}{2577}{section.867.2}\protected@file@percent }
\newlabel{importing-maven-projects}{{867.2}{2577}{Importing Maven Projects}{section.867.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {867.2}{\ignorespaces Dev Studio offers the Maven folder in the Import wizard.}}{2577}{figure.867.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {867.3}Using the POM Graphic Editor}{2577}{section.867.3}\protected@file@percent }
\newlabel{using-the-pom-graphic-editor}{{867.3}{2577}{Using the POM Graphic Editor}{section.867.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {867.3}{\ignorespaces Use the Import Maven Projects wizard to import your pre-existing project.}}{2578}{figure.867.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {867.4}{\ignorespaces Liferay Dev Studio provides five interactive modes to help you edit and organize your POM..}}{2579}{figure.867.4}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {868}IntelliJ}{2581}{chapter.868}\protected@file@percent }
\newlabel{intellij}{{868}{2581}{IntelliJ}{chapter.868}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {869}Installing the Liferay IntelliJ Plugin}{2583}{chapter.869}\protected@file@percent }
\newlabel{installing-the-liferay-intellij-plugin}{{869}{2583}{Installing the Liferay IntelliJ Plugin}{chapter.869}{}}
\@writefile{toc}{\contentsline {section}{\numberline {869.1}Installing Via IntelliJ Marketplace}{2583}{section.869.1}\protected@file@percent }
\newlabel{installing-via-intellij-marketplace}{{869.1}{2583}{Installing Via IntelliJ Marketplace}{section.869.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {869.2}Installing Via Zip File}{2583}{section.869.2}\protected@file@percent }
\newlabel{installing-via-zip-file}{{869.2}{2583}{Installing Via Zip File}{section.869.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {869.1}{\ignorespaces IntelliJ Marketplace offers a streamlined way to install plugins.}}{2584}{figure.869.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {870}Installing a Server in IntelliJ}{2585}{chapter.870}\protected@file@percent }
\newlabel{installing-a-server-in-intellij}{{870}{2585}{Installing a Server in IntelliJ}{chapter.870}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {870.1}{\ignorespaces You have several options to choose from the server dropdown menu.}}{2585}{figure.870.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {870.2}{\ignorespaces Set your Liferay server's configurations in the Run/Debug Configurations menu.}}{2586}{figure.870.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {871}Updating Liferay IntelliJ Plugin}{2587}{chapter.871}\protected@file@percent }
\newlabel{updating-liferay-intellij-plugin}{{871}{2587}{Updating Liferay IntelliJ Plugin}{chapter.871}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {871.1}{\ignorespaces Check for updates periodically to ensure you're leveraging the latest features.}}{2587}{figure.871.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {871.2}{\ignorespaces The Available Updates prompt also prints the plugin version to which you're updating.}}{2588}{figure.871.2}\protected@file@percent }
\gdef \LT@lix {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {872}Liferay JS Generator}{2589}{chapter.872}\protected@file@percent }
\newlabel{liferay-js-generator}{{872}{2589}{Liferay JS Generator}{chapter.872}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {873}Installing the JS Generator and Generating a Bundle}{2591}{chapter.873}\protected@file@percent }
\newlabel{installing-the-js-generator-and-generating-a-bundle}{{873}{2591}{Installing the JS Generator and Generating a Bundle}{chapter.873}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {873.1}{\ignorespaces The liferay-js generator prompts you for widget options.}}{2592}{figure.873.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {874}Understanding the JS Portlet Extender Configuration}{2593}{chapter.874}\protected@file@percent }
\newlabel{understanding-the-js-portlet-extender-configuration}{{874}{2593}{Understanding the JS Portlet Extender Configuration}{chapter.874}{}}
\@writefile{toc}{\contentsline {section}{\numberline {874.1}Manifest Header}{2593}{section.874.1}\protected@file@percent }
\newlabel{manifest-header}{{874.1}{2593}{Manifest Header}{section.874.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {874.2}Main Entry Point}{2593}{section.874.2}\protected@file@percent }
\newlabel{main-entry-point}{{874.2}{2593}{Main Entry Point}{section.874.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {875}Configuration JSON Available Options}{2595}{chapter.875}\protected@file@percent }
\newlabel{configuration-json-available-options}{{875}{2595}{Configuration JSON Available Options}{chapter.875}{}}
\@writefile{toc}{\contentsline {section}{\numberline {875.1}JSON Format}{2595}{section.875.1}\protected@file@percent }
\newlabel{json-format}{{875.1}{2595}{JSON Format}{section.875.1}{}}
\gdef \LT@lx {\LT@entry
{1}{234.8775pt}\LT@entry
{1}{234.8775pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {876}Adapting Existing Apps to Run on Liferay DXP}{2599}{chapter.876}\protected@file@percent }
\newlabel{adapting-existing-apps-to-run-on-liferay-dxp}{{876}{2599}{Adapting Existing Apps to Run on Liferay DXP}{chapter.876}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {876.1}{\ignorespaces You can run the adapt subtarget of the Liferay JS Generator to adapt your existing apps for Liferay.}}{2600}{figure.876.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {876.2}{\ignorespaces You can run the adapt subtarget of the Liferay JS Generator to adapt your existing apps for Liferay.}}{2600}{figure.876.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {876.3}{\ignorespaces Your adapted app runs in Liferay in no time.}}{2601}{figure.876.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {877}Liferay Workspace}{2603}{chapter.877}\protected@file@percent }
\newlabel{liferay-workspace}{{877}{2603}{Liferay Workspace}{chapter.877}{}}
\@writefile{toc}{\contentsline {section}{\numberline {877.1}Workspace Anatomy}{2604}{section.877.1}\protected@file@percent }
\newlabel{workspace-anatomy}{{877.1}{2604}{Workspace Anatomy}{section.877.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {877.2}Development Lifecycle}{2604}{section.877.2}\protected@file@percent }
\newlabel{development-lifecycle}{{877.2}{2604}{Development Lifecycle}{section.877.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {877.3}Creating Projects}{2605}{section.877.3}\protected@file@percent }
\newlabel{creating-projects}{{877.3}{2605}{Creating Projects}{section.877.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {877.4}Building Projects}{2605}{section.877.4}\protected@file@percent }
\newlabel{building-projects}{{877.4}{2605}{Building Projects}{section.877.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {877.5}Deploying Projects}{2606}{section.877.5}\protected@file@percent }
\newlabel{deploying-projects}{{877.5}{2606}{Deploying Projects}{section.877.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {877.6}Testing Projects}{2606}{section.877.6}\protected@file@percent }
\newlabel{testing-projects}{{877.6}{2606}{Testing Projects}{section.877.6}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {877.1}{\ignorespaces The \texttt {configs/common} and \texttt {configs/{[}environment{]}} overlay you Liferay DXP bundle when it's generated.}}{2606}{figure.877.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {877.7}Releasing Projects}{2607}{section.877.7}\protected@file@percent }
\newlabel{releasing-projects}{{877.7}{2607}{Releasing Projects}{section.877.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {878}Installing Liferay Workspace}{2609}{chapter.878}\protected@file@percent }
\newlabel{installing-liferay-workspace}{{878}{2609}{Installing Liferay Workspace}{chapter.878}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {878.1}{\ignorespaces Determine where your Liferay Workspace should reside.}}{2610}{figure.878.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {878.2}{\ignorespaces Select the product version you'll use with your Liferay Workspace.}}{2610}{figure.878.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {879}Creating a Liferay Workspace}{2611}{chapter.879}\protected@file@percent }
\newlabel{creating-a-liferay-workspace}{{879}{2611}{Creating a Liferay Workspace}{chapter.879}{}}
\@writefile{toc}{\contentsline {section}{\numberline {879.1}Blade CLI}{2611}{section.879.1}\protected@file@percent }
\newlabel{blade-cli-3}{{879.1}{2611}{Blade CLI}{section.879.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {879.2}Dev Studio}{2612}{section.879.2}\protected@file@percent }
\newlabel{dev-studio}{{879.2}{2612}{Dev Studio}{section.879.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {879.1}{\ignorespaces By selecting \emph {Liferay Workspace Project}, you begin the process of creating a new workspace for your Liferay projects.}}{2612}{figure.879.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {879.2}{\ignorespaces Dev Studio provides an easy-to-follow menu to create your Liferay Workspace.}}{2613}{figure.879.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {879.3}IntelliJ}{2614}{section.879.3}\protected@file@percent }
\newlabel{intellij-1}{{879.3}{2614}{IntelliJ}{section.879.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {879.3}{\ignorespaces Choose \emph {Liferay Gradle Workspace} or \emph {Liferay Maven Workspace}, depending on the build you prefer.}}{2614}{figure.879.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {879.4}Maven}{2614}{section.879.4}\protected@file@percent }
\newlabel{maven-2}{{879.4}{2614}{Maven}{section.879.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {879.4}{\ignorespaces Specify your workspace's configurations.}}{2615}{figure.879.4}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {880}Importing a Liferay Workspace into an IDE}{2617}{chapter.880}\protected@file@percent }
\newlabel{importing-a-liferay-workspace-into-an-ide}{{880}{2617}{Importing a Liferay Workspace into an IDE}{chapter.880}{}}
\@writefile{toc}{\contentsline {section}{\numberline {880.1}Dev Studio}{2617}{section.880.1}\protected@file@percent }
\newlabel{dev-studio-1}{{880.1}{2617}{Dev Studio}{section.880.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {880.2}IntelliJ}{2617}{section.880.2}\protected@file@percent }
\newlabel{intellij-2}{{880.2}{2617}{IntelliJ}{section.880.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {880.1}{\ignorespaces You can import an existing Liferay Workspace into your current Dev Studio session.}}{2618}{figure.880.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {880.2}{\ignorespaces Specify your workspace's configurations.}}{2618}{figure.880.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {881}Setting Proxy Requirements for Liferay Workspace}{2619}{chapter.881}\protected@file@percent }
\newlabel{setting-proxy-requirements-for-liferay-workspace}{{881}{2619}{Setting Proxy Requirements for Liferay Workspace}{chapter.881}{}}
\@writefile{toc}{\contentsline {section}{\numberline {881.1}Gradle}{2619}{section.881.1}\protected@file@percent }
\newlabel{gradle-1}{{881.1}{2619}{Gradle}{section.881.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {881.2}Maven}{2620}{section.881.2}\protected@file@percent }
\newlabel{maven-3}{{881.2}{2620}{Maven}{section.881.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {882}Adding a Liferay Bundle to Liferay Workspace}{2621}{chapter.882}\protected@file@percent }
\newlabel{adding-a-liferay-bundle-to-liferay-workspace}{{882}{2621}{Adding a Liferay Bundle to Liferay Workspace}{chapter.882}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {883}Setting Environment Configurations for Liferay Workspace}{2623}{chapter.883}\protected@file@percent }
\newlabel{setting-environment-configurations-for-liferay-workspace}{{883}{2623}{Setting Environment Configurations for Liferay Workspace}{chapter.883}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {884}Building Node.js Themes in Liferay Workspace}{2625}{chapter.884}\protected@file@percent }
\newlabel{building-node.js-themes-in-liferay-workspace}{{884}{2625}{Building Node.js Themes in Liferay Workspace}{chapter.884}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {885}Building Gradle/Maven Themes in Liferay Workspace}{2627}{chapter.885}\protected@file@percent }
\newlabel{building-gradlemaven-themes-in-liferay-workspace}{{885}{2627}{Building Gradle/Maven Themes in Liferay Workspace}{chapter.885}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {886}Managing the Target Platform}{2629}{chapter.886}\protected@file@percent }
\newlabel{managing-the-target-platform}{{886}{2629}{Managing the Target Platform}{chapter.886}{}}
\@writefile{toc}{\contentsline {section}{\numberline {886.1}Dependency Management with BOMs}{2629}{section.886.1}\protected@file@percent }
\newlabel{dependency-management-with-boms}{{886.1}{2629}{Dependency Management with BOMs}{section.886.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {886.2}Leveraging Target Platform in Dev Studio}{2630}{section.886.2}\protected@file@percent }
\newlabel{leveraging-target-platform-in-dev-studio}{{886.2}{2630}{Leveraging Target Platform in Dev Studio}{section.886.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {887}Setting the Target Platform}{2631}{chapter.887}\protected@file@percent }
\newlabel{setting-the-target-platform}{{887}{2631}{Setting the Target Platform}{chapter.887}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {888}Targeting a Platform Outside of Workspace}{2633}{chapter.888}\protected@file@percent }
\newlabel{targeting-a-platform-outside-of-workspace}{{888}{2633}{Targeting a Platform Outside of Workspace}{chapter.888}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {889}Targeting a Platform with Maven}{2635}{chapter.889}\protected@file@percent }
\newlabel{targeting-a-platform-with-maven}{{889}{2635}{Targeting a Platform with Maven}{chapter.889}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {890}Validating Modules Against the Target Platform}{2637}{chapter.890}\protected@file@percent }
\newlabel{validating-modules-against-the-target-platform}{{890}{2637}{Validating Modules Against the Target Platform}{chapter.890}{}}
\@writefile{toc}{\contentsline {section}{\numberline {890.1}Resolving Your Modules}{2637}{section.890.1}\protected@file@percent }
\newlabel{resolving-your-modules}{{890.1}{2637}{Resolving Your Modules}{section.890.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {890.2}Modifying the Target Platform's Capabilities}{2639}{section.890.2}\protected@file@percent }
\newlabel{modifying-the-target-platforms-capabilities}{{890.2}{2639}{Modifying the Target Platform's Capabilities}{section.890.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {890.3}Depending on Third Party Libraries Not Included in Liferay DXP}{2639}{section.890.3}\protected@file@percent }
\newlabel{depending-on-third-party-libraries-not-included-in-liferay-dxp}{{890.3}{2639}{Depending on Third Party Libraries Not Included in Liferay DXP}{section.890.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {890.4}Depending on a Customized Distribution of Liferay DXP}{2639}{section.890.4}\protected@file@percent }
\newlabel{depending-on-a-customized-distribution-of-liferay-dxp}{{890.4}{2639}{Depending on a Customized Distribution of Liferay DXP}{section.890.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {890.5}Including the Resolver in Your Gradle Build}{2640}{section.890.5}\protected@file@percent }
\newlabel{including-the-resolver-in-your-gradle-build}{{890.5}{2640}{Including the Resolver in Your Gradle Build}{section.890.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {891}Adding a Third Party Library's Capabilities to the Resolver's Capabilities}{2641}{chapter.891}\protected@file@percent }
\newlabel{adding-a-third-party-librarys-capabilities-to-the-resolvers-capabilities}{{891}{2641}{Adding a Third Party Library's Capabilities to the Resolver's Capabilities}{chapter.891}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {892}Skipping the Resolving Process for a Module}{2643}{chapter.892}\protected@file@percent }
\newlabel{skipping-the-resolving-process-for-a-module}{{892}{2643}{Skipping the Resolving Process for a Module}{chapter.892}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {893}Depending on a Customized Distribution of Liferay DXP}{2645}{chapter.893}\protected@file@percent }
\newlabel{depending-on-a-customized-distribution-of-liferay-dxp-1}{{893}{2645}{Depending on a Customized Distribution of Liferay DXP}{chapter.893}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {894}Including the Resolver in Your Gradle Build}{2647}{chapter.894}\protected@file@percent }
\newlabel{including-the-resolver-in-your-gradle-build-1}{{894}{2647}{Including the Resolver in Your Gradle Build}{chapter.894}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {895}How to Resolve Common Output Errors Reported by the Resolve Task}{2649}{chapter.895}\protected@file@percent }
\newlabel{how-to-resolve-common-output-errors-reported-by-the-resolve-task}{{895}{2649}{How to Resolve Common Output Errors Reported by the Resolve Task}{chapter.895}{}}
\@writefile{toc}{\contentsline {section}{\numberline {895.1}Missing Import Error}{2649}{section.895.1}\protected@file@percent }
\newlabel{missing-import-error}{{895.1}{2649}{Missing Import Error}{section.895.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {895.2}Missing Service Reference}{2650}{section.895.2}\protected@file@percent }
\newlabel{missing-service-reference}{{895.2}{2650}{Missing Service Reference}{section.895.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {895.3}Missing Fragment Host}{2650}{section.895.3}\protected@file@percent }
\newlabel{missing-fragment-host}{{895.3}{2650}{Missing Fragment Host}{section.895.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {896}Validating Modules Outside of Workspace}{2653}{chapter.896}\protected@file@percent }
\newlabel{validating-modules-outside-of-workspace}{{896}{2653}{Validating Modules Outside of Workspace}{chapter.896}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {897}Leveraging Docker}{2655}{chapter.897}\protected@file@percent }
\newlabel{leveraging-docker}{{897}{2655}{Leveraging Docker}{chapter.897}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {898}Creating a Liferay DXP Docker Container}{2657}{chapter.898}\protected@file@percent }
\newlabel{creating-a-liferay-dxp-docker-container}{{898}{2657}{Creating a Liferay DXP Docker Container}{chapter.898}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {899}Configuring a Docker Container}{2659}{chapter.899}\protected@file@percent }
\newlabel{configuring-a-docker-container}{{899}{2659}{Configuring a Docker Container}{chapter.899}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {900}Building a Custom Docker Image}{2661}{chapter.900}\protected@file@percent }
\newlabel{building-a-custom-docker-image}{{900}{2661}{Building a Custom Docker Image}{chapter.900}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {901}Updating Liferay Workspace}{2663}{chapter.901}\protected@file@percent }
\newlabel{updating-liferay-workspace}{{901}{2663}{Updating Liferay Workspace}{chapter.901}{}}
\@writefile{toc}{\contentsline {section}{\numberline {901.1}Gradle}{2663}{section.901.1}\protected@file@percent }
\newlabel{gradle-2}{{901.1}{2663}{Gradle}{section.901.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {901.2}Maven}{2664}{section.901.2}\protected@file@percent }
\newlabel{maven-4}{{901.2}{2664}{Maven}{section.901.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {902}Updating Default Plugins Provided by Liferay Workspace}{2665}{chapter.902}\protected@file@percent }
\newlabel{updating-default-plugins-provided-by-liferay-workspace}{{902}{2665}{Updating Default Plugins Provided by Liferay Workspace}{chapter.902}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {903}Maven}{2667}{chapter.903}\protected@file@percent }
\newlabel{maven-5}{{903}{2667}{Maven}{chapter.903}{}}
\@writefile{toc}{\contentsline {section}{\numberline {903.1}Installing Liferay Maven Artifacts}{2667}{section.903.1}\protected@file@percent }
\newlabel{installing-liferay-maven-artifacts}{{903.1}{2667}{Installing Liferay Maven Artifacts}{section.903.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {903.2}Managing Maven Artifacts in a Repository}{2668}{section.903.2}\protected@file@percent }
\newlabel{managing-maven-artifacts-in-a-repository}{{903.2}{2668}{Managing Maven Artifacts in a Repository}{section.903.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {903.3}Applying Maven Plugins}{2668}{section.903.3}\protected@file@percent }
\newlabel{applying-maven-plugins}{{903.3}{2668}{Applying Maven Plugins}{section.903.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {904}Installing Liferay Maven Artifacts}{2671}{chapter.904}\protected@file@percent }
\newlabel{installing-liferay-maven-artifacts-1}{{904}{2671}{Installing Liferay Maven Artifacts}{chapter.904}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {905}Creating a Maven Repository}{2673}{chapter.905}\protected@file@percent }
\newlabel{creating-a-maven-repository}{{905}{2673}{Creating a Maven Repository}{chapter.905}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {905.1}{\ignorespaces Adding a repository to hold your Liferay artifacts is easy with Nexus.}}{2673}{figure.905.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {906}Configuring Local Maven Settings to Access Repositories}{2675}{chapter.906}\protected@file@percent }
\newlabel{configuring-local-maven-settings-to-access-repositories}{{906}{2675}{Configuring Local Maven Settings to Access Repositories}{chapter.906}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {907}Deploying Liferay Maven Artifacts to a Repository}{2677}{chapter.907}\protected@file@percent }
\newlabel{deploying-liferay-maven-artifacts-to-a-repository}{{907}{2677}{Deploying Liferay Maven Artifacts to a Repository}{chapter.907}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {907.1}{\ignorespaces Your repository server now provides access to your Liferay Maven artifacts.}}{2679}{figure.907.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {908}Building an OSGi Module JAR with Maven}{2681}{chapter.908}\protected@file@percent }
\newlabel{building-an-osgi-module-jar-with-maven}{{908}{2681}{Building an OSGi Module JAR with Maven}{chapter.908}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {909}Building a Theme with Maven}{2683}{chapter.909}\protected@file@percent }
\newlabel{building-a-theme-with-maven}{{909}{2683}{Building a Theme with Maven}{chapter.909}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {910}Compiling Sass Files in a Maven Project}{2687}{chapter.910}\protected@file@percent }
\newlabel{compiling-sass-files-in-a-maven-project}{{910}{2687}{Compiling Sass Files in a Maven Project}{chapter.910}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {911}Using Service Builder in a Maven Project}{2689}{chapter.911}\protected@file@percent }
\newlabel{using-service-builder-in-a-maven-project}{{911}{2689}{Using Service Builder in a Maven Project}{chapter.911}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {912}Upgrading Your Maven Build Environment}{2691}{chapter.912}\protected@file@percent }
\newlabel{upgrading-your-maven-build-environment}{{912}{2691}{Upgrading Your Maven Build Environment}{chapter.912}{}}
\@writefile{toc}{\contentsline {section}{\numberline {912.1}Upgrading to New 7.0 Maven Plugins}{2691}{section.912.1}\protected@file@percent }
\newlabel{upgrading-to-new-7.0-maven-plugins}{{912.1}{2691}{Upgrading to New 7.0 Maven Plugins}{section.912.1}{}}
\gdef \LT@lxi {\LT@entry
{1}{98.84975pt}\LT@entry
{1}{216.28851pt}\LT@entry
{1}{154.56912pt}}
\gdef \LT@lxii {\LT@entry
{1}{143.70718pt}\LT@entry
{3}{131.02863pt}}
\@writefile{toc}{\contentsline {section}{\numberline {912.2}Updating Liferay Maven Artifact Dependencies}{2693}{section.912.2}\protected@file@percent }
\newlabel{updating-liferay-maven-artifact-dependencies}{{912.2}{2693}{Updating Liferay Maven Artifact Dependencies}{section.912.2}{}}
\gdef \LT@lxiii {\LT@entry
{1}{82.53932pt}\LT@entry
{1}{160.56912pt}\LT@entry
{1}{226.60574pt}}
\gdef \LT@lxiv {\LT@entry
{1}{67.33755pt}\LT@entry
{1}{130.61452pt}\LT@entry
{1}{271.80292pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {913}Theme Generator}{2695}{chapter.913}\protected@file@percent }
\newlabel{theme-generator}{{913}{2695}{Theme Generator}{chapter.913}{}}
\@writefile{toc}{\contentsline {section}{\numberline {913.1}Sub-generators}{2695}{section.913.1}\protected@file@percent }
\newlabel{sub-generators}{{913.1}{2695}{Sub-generators}{section.913.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {913.2}Layouts Sub-generator}{2696}{section.913.2}\protected@file@percent }
\newlabel{layouts-sub-generator}{{913.2}{2696}{Layouts Sub-generator}{section.913.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {913.3}Themelets Sub-generator}{2696}{section.913.3}\protected@file@percent }
\newlabel{themelets-sub-generator}{{913.3}{2696}{Themelets Sub-generator}{section.913.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {914}Installing the Theme Generator and Creating a Theme}{2697}{chapter.914}\protected@file@percent }
\newlabel{installing-the-theme-generator-and-creating-a-theme}{{914}{2697}{Installing the Theme Generator and Creating a Theme}{chapter.914}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {914.1}{\ignorespaces The tools are in your hands to create any theme you can imagine.}}{2697}{figure.914.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {914.2}{\ignorespaces You can generate a theme by answering just a few configuration questions.}}{2698}{figure.914.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {915}Generating Layout Templates with the Theme Generator}{2701}{chapter.915}\protected@file@percent }
\newlabel{generating-layout-templates-with-the-theme-generator}{{915}{2701}{Generating Layout Templates with the Theme Generator}{chapter.915}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {915.1}{\ignorespaces The \emph {1-2-1 Columns} page layout creates a nice flow for your content.}}{2701}{figure.915.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {915.2}{\ignorespaces You must specify the width for each column in the row.}}{2702}{figure.915.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {915.3}{\ignorespaces The Layouts sub-generator automates the layout creation process.}}{2703}{figure.915.3}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {915.4}{\ignorespaces Rows can be inserted using the layout vi.}}{2703}{figure.915.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {915.5}{\ignorespaces Rows are removed using the layout vi.}}{2703}{figure.915.5}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {915.6}{\ignorespaces Select the \emph {Finish layout} option to complete your design.}}{2704}{figure.915.6}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {916}Generating Themelets with the Theme Generator}{2705}{chapter.916}\protected@file@percent }
\newlabel{generating-themelets-with-the-theme-generator}{{916}{2705}{Generating Themelets with the Theme Generator}{chapter.916}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {916.1}{\ignorespaces Themelets can be used to modify one aspect of the UI, that you can then reuse in your other themes.}}{2705}{figure.916.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {916.2}{\ignorespaces The Themelet sub-generator automates the themelet creation process, making it quick and easy.}}{2706}{figure.916.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {917}Liferay Upgrade Planner}{2707}{chapter.917}\protected@file@percent }
\newlabel{liferay-upgrade-planner}{{917}{2707}{Liferay Upgrade Planner}{chapter.917}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {917.1}{\ignorespaces Configure your upgrade plan before beginning the upgrade process.}}{2708}{figure.917.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {917.2}{\ignorespaces You can preview the Upgrade Planner's automated updates before you perform them.}}{2709}{figure.917.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {918}Using the Upgrade Planner with Proxy Requirements}{2711}{chapter.918}\protected@file@percent }
\newlabel{using-the-upgrade-planner-with-proxy-requirements}{{918}{2711}{Using the Upgrade Planner with Proxy Requirements}{chapter.918}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {918.1}{\ignorespaces You can configure your proxy settings in Dev Studio's Network Connections menu.}}{2712}{figure.918.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {919}Web Experience Management Reference}{2713}{chapter.919}\protected@file@percent }
\newlabel{web-experience-management-reference}{{919}{2713}{Web Experience Management Reference}{chapter.919}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {920}Fragment Specific Tags}{2715}{chapter.920}\protected@file@percent }
\newlabel{fragment-specific-tags}{{920}{2715}{Fragment Specific Tags}{chapter.920}{}}
\@writefile{toc}{\contentsline {section}{\numberline {920.1}Making Text Editable}{2715}{section.920.1}\protected@file@percent }
\newlabel{making-text-editable}{{920.1}{2715}{Making Text Editable}{section.920.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {920.2}Making Images Editable}{2715}{section.920.2}\protected@file@percent }
\newlabel{making-images-editable}{{920.2}{2715}{Making Images Editable}{section.920.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {920.1}{\ignorespaces You have several options for defining an image on a Content Page.}}{2716}{figure.920.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {920.3}Creating Editable Links}{2716}{section.920.3}\protected@file@percent }
\newlabel{creating-editable-links}{{920.3}{2716}{Creating Editable Links}{section.920.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {920.4}Including Widgets Within A Fragment}{2716}{section.920.4}\protected@file@percent }
\newlabel{including-widgets-within-a-fragment}{{920.4}{2716}{Including Widgets Within A Fragment}{section.920.4}{}}
\gdef \LT@lxv {\LT@entry
{3}{139.77615pt}\LT@entry
{3}{156.03435pt}}
\@writefile{lof}{\contentsline {figure}{\numberline {920.2}{\ignorespaces You have several options for defining a link's appearance and behavior.}}{2717}{figure.920.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {920.5}Enabling Embedding for Your Widget}{2718}{section.920.5}\protected@file@percent }
\newlabel{enabling-embedding-for-your-widget}{{920.5}{2718}{Enabling Embedding for Your Widget}{section.920.5}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {921}Fragment Configuration Types}{2719}{chapter.921}\protected@file@percent }
\newlabel{fragment-configuration-types}{{921}{2719}{Fragment Configuration Types}{chapter.921}{}}
\@writefile{toc}{\contentsline {section}{\numberline {921.1}Checkbox Configuration}{2719}{section.921.1}\protected@file@percent }
\newlabel{checkbox-configuration}{{921.1}{2719}{Checkbox Configuration}{section.921.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {921.1}{\ignorespaces The checkbox configuration is useful when a boolean selection is necessary.}}{2720}{figure.921.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {921.2}Color Palette Configuration}{2720}{section.921.2}\protected@file@percent }
\newlabel{color-palette-configuration}{{921.2}{2720}{Color Palette Configuration}{section.921.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {921.2}{\ignorespaces The \texttt {colorPalette} configuration is useful when a color selection is necessary.}}{2720}{figure.921.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {921.3}Select Configuration}{2721}{section.921.3}\protected@file@percent }
\newlabel{select-configuration}{{921.3}{2721}{Select Configuration}{section.921.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {921.3}{\ignorespaces The \texttt {select} configuration is useful when an option choice is necessary.}}{2721}{figure.921.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {921.4}Text Configuration}{2721}{section.921.4}\protected@file@percent }
\newlabel{text-configuration}{{921.4}{2721}{Text Configuration}{section.921.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {921.4}{\ignorespaces The \texttt {text} configuration is useful when an input text option is necessary.}}{2722}{figure.921.4}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {922}Escaping Fragment Configuration Text Values}{2723}{chapter.922}\protected@file@percent }
\newlabel{escaping-fragment-configuration-text-values}{{922}{2723}{Escaping Fragment Configuration Text Values}{chapter.922}{}}
\@writefile{toc}{\contentsline {section}{\numberline {922.1}Escaping Values in HTML/FreeMarker}{2723}{section.922.1}\protected@file@percent }
\newlabel{escaping-values-in-htmlfreemarker}{{922.1}{2723}{Escaping Values in HTML/FreeMarker}{section.922.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {922.2}Escaping Values in JavaScript}{2723}{section.922.2}\protected@file@percent }
\newlabel{escaping-values-in-javascript}{{922.2}{2723}{Escaping Values in JavaScript}{section.922.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {923}Asset Display Page Taglib}{2725}{chapter.923}\protected@file@percent }
\newlabel{asset-display-page-taglib}{{923}{2725}{Asset Display Page Taglib}{chapter.923}{}}
\@writefile{toc}{\contentsline {section}{\numberline {923.1}Display Page Attributes}{2725}{section.923.1}\protected@file@percent }
\newlabel{display-page-attributes}{{923.1}{2725}{Display Page Attributes}{section.923.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {924}Crafting XML Workflow Definitions}{2727}{chapter.924}\protected@file@percent }
\newlabel{crafting-xml-workflow-definitions}{{924}{2727}{Crafting XML Workflow Definitions}{chapter.924}{}}
\@writefile{toc}{\contentsline {section}{\numberline {924.1}Existing Workflow Definitions}{2727}{section.924.1}\protected@file@percent }
\newlabel{existing-workflow-definitions}{{924.1}{2727}{Existing Workflow Definitions}{section.924.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {924.2}Schema}{2728}{section.924.2}\protected@file@percent }
\newlabel{schema}{{924.2}{2728}{Schema}{section.924.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {924.3}Metadata}{2728}{section.924.3}\protected@file@percent }
\newlabel{metadata}{{924.3}{2728}{Metadata}{section.924.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {925}Workflow Definition Nodes}{2729}{chapter.925}\protected@file@percent }
\newlabel{workflow-definition-nodes}{{925}{2729}{Workflow Definition Nodes}{chapter.925}{}}
\@writefile{toc}{\contentsline {section}{\numberline {925.1}State Nodes}{2729}{section.925.1}\protected@file@percent }
\newlabel{state-nodes}{{925.1}{2729}{State Nodes}{section.925.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {925.2}Conditions}{2730}{section.925.2}\protected@file@percent }
\newlabel{conditions}{{925.2}{2730}{Conditions}{section.925.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {925.3}Forks and Joins}{2731}{section.925.3}\protected@file@percent }
\newlabel{forks-and-joins}{{925.3}{2731}{Forks and Joins}{section.925.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {925.4}Task Nodes}{2732}{section.925.4}\protected@file@percent }
\newlabel{task-nodes}{{925.4}{2732}{Task Nodes}{section.925.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {926}Workflow Task Nodes}{2733}{chapter.926}\protected@file@percent }
\newlabel{workflow-task-nodes}{{926}{2733}{Workflow Task Nodes}{chapter.926}{}}
\@writefile{toc}{\contentsline {section}{\numberline {926.1}Assignments}{2734}{section.926.1}\protected@file@percent }
\newlabel{assignments}{{926.1}{2734}{Assignments}{section.926.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {926.2}Resource Action Assignments}{2735}{section.926.2}\protected@file@percent }
\newlabel{resource-action-assignments}{{926.2}{2735}{Resource Action Assignments}{section.926.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {926.3}Task Timers}{2736}{section.926.3}\protected@file@percent }
\newlabel{task-timers}{{926.3}{2736}{Task Timers}{section.926.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {927}Workflow Notifications}{2739}{chapter.927}\protected@file@percent }
\newlabel{workflow-notifications}{{927}{2739}{Workflow Notifications}{chapter.927}{}}
\@writefile{toc}{\contentsline {section}{\numberline {927.1}Notification Options}{2739}{section.927.1}\protected@file@percent }
\newlabel{notification-options}{{927.1}{2739}{Notification Options}{section.927.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {928}Breaking Changes}{2743}{chapter.928}\protected@file@percent }
\newlabel{breaking-changes}{{928}{2743}{Breaking Changes}{chapter.928}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.1}Breaking Changes List}{2743}{section.928.1}\protected@file@percent }
\newlabel{breaking-changes-list}{{928.1}{2743}{Breaking Changes List}{section.928.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.2}Removed Support for JSP Templates in Themes}{2743}{section.928.2}\protected@file@percent }
\newlabel{removed-support-for-jsp-templates-in-themes}{{928.2}{2743}{Removed Support for JSP Templates in Themes}{section.928.2}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2743}{section.928.2}\protected@file@percent }
\newlabel{what-changed}{{928.2}{2743}{What changed?}{section.928.2}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2744}{section.928.2}\protected@file@percent }
\newlabel{who-is-affected}{{928.2}{2744}{Who is affected?}{section.928.2}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2744}{section.928.2}\protected@file@percent }
\newlabel{how-should-i-update-my-code}{{928.2}{2744}{How should I update my code?}{section.928.2}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2744}{section.928.2}\protected@file@percent }
\newlabel{why-was-this-change-made}{{928.2}{2744}{Why was this change made?}{section.928.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.3}Lodash Is No Longer Included by Default}{2744}{section.928.3}\protected@file@percent }
\newlabel{lodash-is-no-longer-included-by-default}{{928.3}{2744}{Lodash Is No Longer Included by Default}{section.928.3}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2744}{section.928.3}\protected@file@percent }
\newlabel{what-changed-1}{{928.3}{2744}{What changed?}{section.928.3}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2744}{section.928.3}\protected@file@percent }
\newlabel{who-is-affected-1}{{928.3}{2744}{Who is affected?}{section.928.3}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2744}{section.928.3}\protected@file@percent }
\newlabel{how-should-i-update-my-code-1}{{928.3}{2744}{How should I update my code?}{section.928.3}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2744}{section.928.3}\protected@file@percent }
\newlabel{why-was-this-change-made-1}{{928.3}{2744}{Why was this change made?}{section.928.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.4}Moved Two Staging Properties to OSGi Configuration}{2744}{section.928.4}\protected@file@percent }
\newlabel{moved-two-staging-properties-to-osgi-configuration}{{928.4}{2744}{Moved Two Staging Properties to OSGi Configuration}{section.928.4}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2745}{section.928.4}\protected@file@percent }
\newlabel{what-changed-2}{{928.4}{2745}{What changed?}{section.928.4}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2745}{section.928.4}\protected@file@percent }
\newlabel{who-is-affected-2}{{928.4}{2745}{Who is affected?}{section.928.4}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2745}{section.928.4}\protected@file@percent }
\newlabel{how-should-i-update-my-code-2}{{928.4}{2745}{How should I update my code?}{section.928.4}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2745}{section.928.4}\protected@file@percent }
\newlabel{why-was-this-change-made-2}{{928.4}{2745}{Why was this change made?}{section.928.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.5}Remove Link Application URLs to Page Functionality}{2745}{section.928.5}\protected@file@percent }
\newlabel{remove-link-application-urls-to-page-functionality}{{928.5}{2745}{Remove Link Application URLs to Page Functionality}{section.928.5}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2745}{section.928.5}\protected@file@percent }
\newlabel{what-changed-3}{{928.5}{2745}{What changed?}{section.928.5}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2745}{section.928.5}\protected@file@percent }
\newlabel{who-is-affected-3}{{928.5}{2745}{Who is affected?}{section.928.5}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2745}{section.928.5}\protected@file@percent }
\newlabel{how-should-i-update-my-code-3}{{928.5}{2745}{How should I update my code?}{section.928.5}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2745}{section.928.5}\protected@file@percent }
\newlabel{why-was-this-change-made-3}{{928.5}{2745}{Why was this change made?}{section.928.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.6}Moved TermsOfUseContentProvider out of kernel.util}{2746}{section.928.6}\protected@file@percent }
\newlabel{moved-termsofusecontentprovider-out-of-kernel.util}{{928.6}{2746}{Moved TermsOfUseContentProvider out of kernel.util}{section.928.6}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2746}{section.928.6}\protected@file@percent }
\newlabel{what-changed-4}{{928.6}{2746}{What changed?}{section.928.6}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2746}{section.928.6}\protected@file@percent }
\newlabel{who-is-affected-4}{{928.6}{2746}{Who is affected?}{section.928.6}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2746}{section.928.6}\protected@file@percent }
\newlabel{how-should-i-update-my-code-4}{{928.6}{2746}{How should I update my code?}{section.928.6}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2746}{section.928.6}\protected@file@percent }
\newlabel{why-was-this-change-made-4}{{928.6}{2746}{Why was this change made?}{section.928.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.7}Removed HibernateConfigurationConverter and Converter}{2746}{section.928.7}\protected@file@percent }
\newlabel{removed-hibernateconfigurationconverter-and-converter}{{928.7}{2746}{Removed HibernateConfigurationConverter and Converter}{section.928.7}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2746}{section.928.7}\protected@file@percent }
\newlabel{what-changed-5}{{928.7}{2746}{What changed?}{section.928.7}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2747}{section.928.7}\protected@file@percent }
\newlabel{who-is-affected-5}{{928.7}{2747}{Who is affected?}{section.928.7}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2747}{section.928.7}\protected@file@percent }
\newlabel{how-should-i-update-my-code-5}{{928.7}{2747}{How should I update my code?}{section.928.7}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2747}{section.928.7}\protected@file@percent }
\newlabel{why-was-this-change-made-5}{{928.7}{2747}{Why was this change made?}{section.928.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.8}Switched to Use JDK Function and Supplier}{2747}{section.928.8}\protected@file@percent }
\newlabel{switched-to-use-jdk-function-and-supplier}{{928.8}{2747}{Switched to Use JDK Function and Supplier}{section.928.8}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2747}{section.928.8}\protected@file@percent }
\newlabel{what-changed-6}{{928.8}{2747}{What changed?}{section.928.8}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2747}{section.928.8}\protected@file@percent }
\newlabel{who-is-affected-6}{{928.8}{2747}{Who is affected?}{section.928.8}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2747}{section.928.8}\protected@file@percent }
\newlabel{how-should-i-update-my-code-6}{{928.8}{2747}{How should I update my code?}{section.928.8}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2747}{section.928.8}\protected@file@percent }
\newlabel{why-was-this-change-made-6}{{928.8}{2747}{Why was this change made?}{section.928.8}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.9}Deprecated com.liferay.portal.service.InvokableService Interface}{2747}{section.928.9}\protected@file@percent }
\newlabel{deprecated-com.liferay.portal.service.invokableservice-interface}{{928.9}{2747}{Deprecated com.liferay.portal.service.InvokableService Interface}{section.928.9}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2747}{section.928.9}\protected@file@percent }
\newlabel{what-changed-7}{{928.9}{2747}{What changed?}{section.928.9}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2748}{section.928.9}\protected@file@percent }
\newlabel{who-is-affected-7}{{928.9}{2748}{Who is affected?}{section.928.9}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2748}{section.928.9}\protected@file@percent }
\newlabel{how-should-i-update-my-code-7}{{928.9}{2748}{How should I update my code?}{section.928.9}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2748}{section.928.9}\protected@file@percent }
\newlabel{why-was-this-change-made-7}{{928.9}{2748}{Why was this change made?}{section.928.9}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.10}Dropped Support of ServiceLoaderCondition}{2748}{section.928.10}\protected@file@percent }
\newlabel{dropped-support-of-serviceloadercondition}{{928.10}{2748}{Dropped Support of ServiceLoaderCondition}{section.928.10}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2748}{section.928.10}\protected@file@percent }
\newlabel{what-changed-8}{{928.10}{2748}{What changed?}{section.928.10}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2748}{section.928.10}\protected@file@percent }
\newlabel{who-is-affected-8}{{928.10}{2748}{Who is affected?}{section.928.10}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2748}{section.928.10}\protected@file@percent }
\newlabel{how-should-i-update-my-code-8}{{928.10}{2748}{How should I update my code?}{section.928.10}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2748}{section.928.10}\protected@file@percent }
\newlabel{why-was-this-change-made-8}{{928.10}{2748}{Why was this change made?}{section.928.10}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.11}Switched to Use JDK Predicate}{2748}{section.928.11}\protected@file@percent }
\newlabel{switched-to-use-jdk-predicate}{{928.11}{2748}{Switched to Use JDK Predicate}{section.928.11}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2749}{section.928.11}\protected@file@percent }
\newlabel{what-changed-9}{{928.11}{2749}{What changed?}{section.928.11}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2749}{section.928.11}\protected@file@percent }
\newlabel{who-is-affected-9}{{928.11}{2749}{Who is affected?}{section.928.11}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2749}{section.928.11}\protected@file@percent }
\newlabel{how-should-i-update-my-code-9}{{928.11}{2749}{How should I update my code?}{section.928.11}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2749}{section.928.11}\protected@file@percent }
\newlabel{why-was-this-change-made-9}{{928.11}{2749}{Why was this change made?}{section.928.11}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.12}Removed Unsafe Functional Interfaces in Package com.liferay.portal.kernel.util}{2749}{section.928.12}\protected@file@percent }
\newlabel{removed-unsafe-functional-interfaces-in-package-com.liferay.portal.kernel.util}{{928.12}{2749}{Removed Unsafe Functional Interfaces in Package com.liferay.portal.kernel.util}{section.928.12}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2749}{section.928.12}\protected@file@percent }
\newlabel{what-changed-10}{{928.12}{2749}{What changed?}{section.928.12}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2749}{section.928.12}\protected@file@percent }
\newlabel{who-is-affected-10}{{928.12}{2749}{Who is affected?}{section.928.12}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2750}{section.928.12}\protected@file@percent }
\newlabel{how-should-i-update-my-code-10}{{928.12}{2750}{How should I update my code?}{section.928.12}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2750}{section.928.12}\protected@file@percent }
\newlabel{why-was-this-change-made-10}{{928.12}{2750}{Why was this change made?}{section.928.12}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.13}Deprecated NTLM in Portal Distribution}{2750}{section.928.13}\protected@file@percent }
\newlabel{deprecated-ntlm-in-portal-distribution}{{928.13}{2750}{Deprecated NTLM in Portal Distribution}{section.928.13}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2750}{section.928.13}\protected@file@percent }
\newlabel{what-changed-11}{{928.13}{2750}{What changed?}{section.928.13}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2750}{section.928.13}\protected@file@percent }
\newlabel{who-is-affected-11}{{928.13}{2750}{Who is affected?}{section.928.13}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2750}{section.928.13}\protected@file@percent }
\newlabel{how-should-i-update-my-code-11}{{928.13}{2750}{How should I update my code?}{section.928.13}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2750}{section.928.13}\protected@file@percent }
\newlabel{why-was-this-change-made-11}{{928.13}{2750}{Why was this change made?}{section.928.13}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.14}Deprecated OpenID in Portal Distribution}{2750}{section.928.14}\protected@file@percent }
\newlabel{deprecated-openid-in-portal-distribution}{{928.14}{2750}{Deprecated OpenID in Portal Distribution}{section.928.14}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2750}{section.928.14}\protected@file@percent }
\newlabel{what-changed-12}{{928.14}{2750}{What changed?}{section.928.14}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2751}{section.928.14}\protected@file@percent }
\newlabel{who-is-affected-12}{{928.14}{2751}{Who is affected?}{section.928.14}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2751}{section.928.14}\protected@file@percent }
\newlabel{how-should-i-update-my-code-12}{{928.14}{2751}{How should I update my code?}{section.928.14}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2751}{section.928.14}\protected@file@percent }
\newlabel{why-was-this-change-made-12}{{928.14}{2751}{Why was this change made?}{section.928.14}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.15}Deprecated Google SSO in Portal Distribution}{2751}{section.928.15}\protected@file@percent }
\newlabel{deprecated-google-sso-in-portal-distribution}{{928.15}{2751}{Deprecated Google SSO in Portal Distribution}{section.928.15}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2751}{section.928.15}\protected@file@percent }
\newlabel{what-changed-13}{{928.15}{2751}{What changed?}{section.928.15}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2751}{section.928.15}\protected@file@percent }
\newlabel{who-is-affected-13}{{928.15}{2751}{Who is affected?}{section.928.15}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2751}{section.928.15}\protected@file@percent }
\newlabel{how-should-i-update-my-code-13}{{928.15}{2751}{How should I update my code?}{section.928.15}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2751}{section.928.15}\protected@file@percent }
\newlabel{why-was-this-change-made-13}{{928.15}{2751}{Why was this change made?}{section.928.15}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.16}Updated AlloyEditor v2.0 Includes New Major Version of React}{2751}{section.928.16}\protected@file@percent }
\newlabel{updated-alloyeditor-v2.0-includes-new-major-version-of-react}{{928.16}{2751}{Updated AlloyEditor v2.0 Includes New Major Version of React}{section.928.16}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2752}{section.928.16}\protected@file@percent }
\newlabel{what-changed-14}{{928.16}{2752}{What changed?}{section.928.16}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2752}{section.928.16}\protected@file@percent }
\newlabel{who-is-affected-14}{{928.16}{2752}{Who is affected?}{section.928.16}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2752}{section.928.16}\protected@file@percent }
\newlabel{how-should-i-update-my-code-14}{{928.16}{2752}{How should I update my code?}{section.928.16}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2752}{section.928.16}\protected@file@percent }
\newlabel{why-was-this-change-made-14}{{928.16}{2752}{Why was this change made?}{section.928.16}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.17}Deprecated dl.tabs.visible property}{2752}{section.928.17}\protected@file@percent }
\newlabel{deprecated-dl.tabs.visible-property}{{928.17}{2752}{Deprecated dl.tabs.visible property}{section.928.17}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2752}{section.928.17}\protected@file@percent }
\newlabel{what-changed-15}{{928.17}{2752}{What changed?}{section.928.17}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2752}{section.928.17}\protected@file@percent }
\newlabel{who-is-affected-15}{{928.17}{2752}{Who is affected?}{section.928.17}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2752}{section.928.17}\protected@file@percent }
\newlabel{how-should-i-update-my-code-15}{{928.17}{2752}{How should I update my code?}{section.928.17}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2752}{section.928.17}\protected@file@percent }
\newlabel{why-was-this-change-made-15}{{928.17}{2752}{Why was this change made?}{section.928.17}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.18}Move the User Menu out of the Product Menu}{2753}{section.928.18}\protected@file@percent }
\newlabel{move-the-user-menu-out-of-the-product-menu}{{928.18}{2753}{Move the User Menu out of the Product Menu}{section.928.18}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2753}{section.928.18}\protected@file@percent }
\newlabel{what-changed-16}{{928.18}{2753}{What changed?}{section.928.18}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2753}{section.928.18}\protected@file@percent }
\newlabel{who-is-affected-16}{{928.18}{2753}{Who is affected?}{section.928.18}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2753}{section.928.18}\protected@file@percent }
\newlabel{how-should-i-update-my-code-16}{{928.18}{2753}{How should I update my code?}{section.928.18}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2753}{section.928.18}\protected@file@percent }
\newlabel{why-was-this-change-made-16}{{928.18}{2753}{Why was this change made?}{section.928.18}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.19}Removed Hong Kong and Macau from the List of Countries}{2753}{section.928.19}\protected@file@percent }
\newlabel{removed-hong-kong-and-macau-from-the-list-of-countries}{{928.19}{2753}{Removed Hong Kong and Macau from the List of Countries}{section.928.19}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2753}{section.928.19}\protected@file@percent }
\newlabel{what-changed-17}{{928.19}{2753}{What changed?}{section.928.19}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2753}{section.928.19}\protected@file@percent }
\newlabel{who-is-affected-17}{{928.19}{2753}{Who is affected?}{section.928.19}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2753}{section.928.19}\protected@file@percent }
\newlabel{how-should-i-update-my-code-17}{{928.19}{2753}{How should I update my code?}{section.928.19}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2754}{section.928.19}\protected@file@percent }
\newlabel{why-was-this-change-made-17}{{928.19}{2754}{Why was this change made?}{section.928.19}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.20}JGroups Was Upgraded From 3.6.16 to 4.1.1}{2754}{section.928.20}\protected@file@percent }
\newlabel{jgroups-was-upgraded-from-3.6.16-to-4.1.1}{{928.20}{2754}{JGroups Was Upgraded From 3.6.16 to 4.1.1}{section.928.20}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2754}{section.928.20}\protected@file@percent }
\newlabel{what-changed-18}{{928.20}{2754}{What changed?}{section.928.20}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2754}{section.928.20}\protected@file@percent }
\newlabel{who-is-affected-18}{{928.20}{2754}{Who is affected?}{section.928.20}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2754}{section.928.20}\protected@file@percent }
\newlabel{how-should-i-update-my-code-18}{{928.20}{2754}{How should I update my code?}{section.928.20}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2754}{section.928.20}\protected@file@percent }
\newlabel{why-was-this-change-made-18}{{928.20}{2754}{Why was this change made?}{section.928.20}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.21}Liferay \texttt {AssetEntries\_AssetCategories} Is No Longer Used}{2754}{section.928.21}\protected@file@percent }
\newlabel{liferay-assetentries_assetcategories-is-no-longer-used}{{928.21}{2754}{\texorpdfstring {Liferay \texttt {AssetEntries\_AssetCategories} Is No Longer Used}{Liferay AssetEntries\_AssetCategories Is No Longer Used}}{section.928.21}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2754}{section.928.21}\protected@file@percent }
\newlabel{what-changed-19}{{928.21}{2754}{What changed?}{section.928.21}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2754}{section.928.21}\protected@file@percent }
\newlabel{who-is-affected-19}{{928.21}{2754}{Who is affected?}{section.928.21}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2755}{section.928.21}\protected@file@percent }
\newlabel{how-should-i-update-my-code-19}{{928.21}{2755}{How should I update my code?}{section.928.21}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2755}{section.928.21}\protected@file@percent }
\newlabel{why-was-this-change-made-19}{{928.21}{2755}{Why was this change made?}{section.928.21}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.22}Auto Tagging Must Be Reconfigured Manually}{2755}{section.928.22}\protected@file@percent }
\newlabel{auto-tagging-must-be-reconfigured-manually}{{928.22}{2755}{Auto Tagging Must Be Reconfigured Manually}{section.928.22}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2755}{section.928.22}\protected@file@percent }
\newlabel{what-changed-20}{{928.22}{2755}{What changed?}{section.928.22}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2755}{section.928.22}\protected@file@percent }
\newlabel{who-is-affected-20}{{928.22}{2755}{Who is affected?}{section.928.22}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2756}{section.928.22}\protected@file@percent }
\newlabel{how-should-i-update-my-code-20}{{928.22}{2756}{How should I update my code?}{section.928.22}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2756}{section.928.22}\protected@file@percent }
\newlabel{why-was-this-change-made-20}{{928.22}{2756}{Why was this change made?}{section.928.22}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.23}Blogs Image Properties Were Moved to System Settings}{2756}{section.928.23}\protected@file@percent }
\newlabel{blogs-image-properties-were-moved-to-system-settings}{{928.23}{2756}{Blogs Image Properties Were Moved to System Settings}{section.928.23}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2756}{section.928.23}\protected@file@percent }
\newlabel{what-changed-21}{{928.23}{2756}{What changed?}{section.928.23}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2756}{section.928.23}\protected@file@percent }
\newlabel{who-is-affected-21}{{928.23}{2756}{Who is affected?}{section.928.23}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2756}{section.928.23}\protected@file@percent }
\newlabel{how-should-i-update-my-code-21}{{928.23}{2756}{How should I update my code?}{section.928.23}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2756}{section.928.23}\protected@file@percent }
\newlabel{why-was-this-change-made-21}{{928.23}{2756}{Why was this change made?}{section.928.23}{}}
\@writefile{toc}{\contentsline {section}{\numberline {928.24}Removed Cache Bootstrap Feature}{2756}{section.928.24}\protected@file@percent }
\newlabel{removed-cache-bootstrap-feature}{{928.24}{2756}{Removed Cache Bootstrap Feature}{section.928.24}{}}
\@writefile{toc}{\contentsline {subsection}{What changed?}{2756}{section.928.24}\protected@file@percent }
\newlabel{what-changed-22}{{928.24}{2756}{What changed?}{section.928.24}{}}
\@writefile{toc}{\contentsline {subsection}{Who is affected?}{2757}{section.928.24}\protected@file@percent }
\newlabel{who-is-affected-22}{{928.24}{2757}{Who is affected?}{section.928.24}{}}
\@writefile{toc}{\contentsline {subsection}{How should I update my code?}{2757}{section.928.24}\protected@file@percent }
\newlabel{how-should-i-update-my-code-22}{{928.24}{2757}{How should I update my code?}{section.928.24}{}}
\@writefile{toc}{\contentsline {subsection}{Why was this change made?}{2757}{section.928.24}\protected@file@percent }
\newlabel{why-was-this-change-made-22}{{928.24}{2757}{Why was this change made?}{section.928.24}{}}
\gdef \LT@lxvi {\LT@entry
{3}{121.02634pt}\LT@entry
{3}{107.02176pt}\LT@entry
{1}{55.19775pt}\LT@entry
{3}{125.4974pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {929}CDI Portlet Predefined Beans}{2759}{chapter.929}\protected@file@percent }
\newlabel{cdi-portlet-predefined-beans}{{929}{2759}{CDI Portlet Predefined Beans}{chapter.929}{}}
\@writefile{toc}{\contentsline {section}{\numberline {929.1}Portlet Request Scoped Beans}{2759}{section.929.1}\protected@file@percent }
\newlabel{portlet-request-scoped-beans}{{929.1}{2759}{Portlet Request Scoped Beans}{section.929.1}{}}
\gdef \LT@lxvii {\LT@entry
{3}{105.13809pt}\LT@entry
{1}{83.6349pt}\LT@entry
{3}{72.01375pt}\LT@entry
{1}{65.72127pt}}
\@writefile{toc}{\contentsline {section}{\numberline {929.2}Dependent Scoped Beans}{2760}{section.929.2}\protected@file@percent }
\newlabel{dependent-scoped-beans}{{929.2}{2760}{Dependent Scoped Beans}{section.929.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {929.3}Related Topics}{2760}{section.929.3}\protected@file@percent }
\newlabel{related-topics-49}{{929.3}{2760}{Related Topics}{section.929.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {930}Item Selector Criterion and Return Types}{2761}{chapter.930}\protected@file@percent }
\newlabel{item-selector-criterion-and-return-types}{{930}{2761}{Item Selector Criterion and Return Types}{chapter.930}{}}
\@writefile{toc}{\contentsline {section}{\numberline {930.1}Item Selector Criterion Classes}{2761}{section.930.1}\protected@file@percent }
\newlabel{item-selector-criterion-classes}{{930.1}{2761}{Item Selector Criterion Classes}{section.930.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {930.2}Item Selector Return Type Classes}{2762}{section.930.2}\protected@file@percent }
\newlabel{item-selector-return-type-classes}{{930.2}{2762}{Item Selector Return Type Classes}{section.930.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {931}Java APIs}{2763}{chapter.931}\protected@file@percent }
\newlabel{java-apis}{{931}{2763}{Java APIs}{chapter.931}{}}
\@writefile{toc}{\contentsline {section}{\numberline {931.1}7.0 Java APIs}{2763}{section.931.1}\protected@file@percent }
\newlabel{java-apis-1}{{931.1}{2763}{7.0 Java APIs}{section.931.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {931.2}Liferay DXP App Java APIs}{2763}{section.931.2}\protected@file@percent }
\newlabel{liferay-dxp-app-java-apis}{{931.2}{2763}{Liferay DXP App Java APIs}{section.931.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {931.3}Forms and Workflow}{2764}{section.931.3}\protected@file@percent }
\newlabel{forms-and-workflow}{{931.3}{2764}{Forms and Workflow}{section.931.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {931.4}Foundation}{2765}{section.931.4}\protected@file@percent }
\newlabel{foundation}{{931.4}{2765}{Foundation}{section.931.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {931.5}Web Experience}{2767}{section.931.5}\protected@file@percent }
\newlabel{web-experience}{{931.5}{2767}{Web Experience}{section.931.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {931.6}JavaScript and CSS}{2768}{section.931.6}\protected@file@percent }
\newlabel{javascript-and-css}{{931.6}{2768}{JavaScript and CSS}{section.931.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {931.7}Descriptor Definitions}{2768}{section.931.7}\protected@file@percent }
\newlabel{descriptor-definitions}{{931.7}{2768}{Descriptor Definitions}{section.931.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {932}Meaningful Schema Versioning}{2769}{chapter.932}\protected@file@percent }
\newlabel{meaningful-schema-versioning}{{932}{2769}{Meaningful Schema Versioning}{chapter.932}{}}
\@writefile{toc}{\contentsline {section}{\numberline {932.1}Micro change examples}{2770}{section.932.1}\protected@file@percent }
\newlabel{micro-change-examples}{{932.1}{2770}{Micro change examples}{section.932.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {932.2}Minor change examples}{2770}{section.932.2}\protected@file@percent }
\newlabel{minor-change-examples}{{932.2}{2770}{Minor change examples}{section.932.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {932.3}Major change examples}{2770}{section.932.3}\protected@file@percent }
\newlabel{major-change-examples}{{932.3}{2770}{Major change examples}{section.932.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {933}Portlet 3.0 API Opt In}{2771}{chapter.933}\protected@file@percent }
\newlabel{portlet-3.0-api-opt-in}{{933}{2771}{Portlet 3.0 API Opt In}{chapter.933}{}}
\@writefile{toc}{\contentsline {section}{\numberline {933.1}Standard Portlet \texttt {@PortletApplication} Annotation}{2771}{section.933.1}\protected@file@percent }
\newlabel{standard-portlet-portletapplication-annotation}{{933.1}{2771}{\texorpdfstring {Standard Portlet \texttt {@PortletApplication} Annotation}{Standard Portlet @PortletApplication Annotation}}{section.933.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {933.2}Liferay MVC Portlet \texttt {@Component} Annotation}{2771}{section.933.2}\protected@file@percent }
\newlabel{liferay-mvc-portlet-component-annotation}{{933.2}{2771}{\texorpdfstring {Liferay MVC Portlet \texttt {@Component} Annotation}{Liferay MVC Portlet @Component Annotation}}{section.933.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {933.3}\texttt {portlet.xml} Descriptor}{2771}{section.933.3}\protected@file@percent }
\newlabel{portlet.xml-descriptor}{{933.3}{2771}{\texorpdfstring {\texttt {portlet.xml} Descriptor}{portlet.xml Descriptor}}{section.933.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {934}Portlet Descriptor to OSGi Service Property Map}{2773}{chapter.934}\protected@file@percent }
\newlabel{portlet-descriptor-to-osgi-service-property-map}{{934}{2773}{Portlet Descriptor to OSGi Service Property Map}{chapter.934}{}}
\@writefile{toc}{\contentsline {section}{\numberline {934.1}Portlet Descriptor Mappings}{2774}{section.934.1}\protected@file@percent }
\newlabel{portlet-descriptor-mappings}{{934.1}{2774}{Portlet Descriptor Mappings}{section.934.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {934.2}Liferay Descriptor Mappings}{2774}{section.934.2}\protected@file@percent }
\newlabel{liferay-descriptor-mappings}{{934.2}{2774}{Liferay Descriptor Mappings}{section.934.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {934.3}Liferay Display}{2775}{section.934.3}\protected@file@percent }
\newlabel{liferay-display}{{934.3}{2775}{Liferay Display}{section.934.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {934.4}Liferay Portlet}{2775}{section.934.4}\protected@file@percent }
\newlabel{liferay-portlet}{{934.4}{2775}{Liferay Portlet}{section.934.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {935}Third Party Packages Portal Exports}{2779}{chapter.935}\protected@file@percent }
\newlabel{third-party-packages-portal-exports}{{935}{2779}{Third Party Packages Portal Exports}{chapter.935}{}}
\@setckpt{developer/reference}{
\setcounter{page}{2780}
\setcounter{equation}{0}
\setcounter{enumi}{2}
\setcounter{enumii}{5}
\setcounter{enumiii}{3}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{@memmarkcntra}{-1}
\setcounter{storedpagenumber}{1}
\setcounter{book}{0}
\setcounter{part}{5}
\setcounter{chapter}{935}
\setcounter{section}{0}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{@ppsavesec}{0}
\setcounter{@ppsaveapp}{0}
\setcounter{vslineno}{0}
\setcounter{poemline}{0}
\setcounter{modulo@vs}{0}
\setcounter{memfvsline}{0}
\setcounter{verse}{0}
\setcounter{chrsinstr}{0}
\setcounter{poem}{0}
\setcounter{newflo@tctr}{4}
\setcounter{@contsubnum}{0}
\setcounter{section@level}{0}
\setcounter{maxsecnumdepth}{1}
\setcounter{sidefootnote}{0}
\setcounter{pagenote}{0}
\setcounter{pagenoteshadow}{0}
\setcounter{memfbvline}{0}
\setcounter{bvlinectr}{0}
\setcounter{cp@cntr}{0}
\setcounter{ism@mctr}{0}
\setcounter{xsm@mctr}{0}
\setcounter{csm@mctr}{0}
\setcounter{ksm@mctr}{0}
\setcounter{xksm@mctr}{0}
\setcounter{cksm@mctr}{0}
\setcounter{msm@mctr}{0}
\setcounter{xmsm@mctr}{0}
\setcounter{cmsm@mctr}{0}
\setcounter{bsm@mctr}{0}
\setcounter{workm@mctr}{0}
\setcounter{sheetsequence}{2852}
\setcounter{lastsheet}{2851}
\setcounter{lastpage}{2779}
\setcounter{figure}{0}
\setcounter{lofdepth}{1}
\setcounter{table}{0}
\setcounter{lotdepth}{1}
\setcounter{Item}{2549}
\setcounter{Hfootnote}{7}
\setcounter{bookmark@seq@number}{0}
\setcounter{memhycontfloat}{0}
\setcounter{Hpagenote}{0}
\setcounter{r@tfl@t}{0}
\setcounter{float@type}{4}
\setcounter{LT@tables}{67}
\setcounter{LT@chunks}{3}
\setcounter{parentequation}{0}
\setcounter{FancyVerbLine}{0}
}
================================================
FILE: book/developer/reference.tex
================================================
\chapter{Developer Reference}\label{developer-reference}
This developer reference contains lists of options for various APIs. The
actual API reference is stored on
\href{https://docs.liferay.com}{docs.liferay.com}. Here, you'll find
higher level descriptions of those APIs, lists of tag libraries,
descriptions of Gradle and Maven plugins, and much more.
One highlight here is a complete description of Liferay's development
tooling. This includes not only our Blade CLI (a tool that bridges the
gap between Gradle and Maven, bringing archetype-like functionality to
Liferay Gradle projects), but also our plugins for IntelliJ and Eclipse,
not to mention our Maven or Gradle-based Liferay Workspace. It also
includes a complete description of our JS Generator, which helps
front-end developers create pure JavaScript widgets.
\chapter{Classes Moved From
portal-service.jar}\label{classes-moved-from-portal-service.jar}
To leverage the benefits of modularization, many classes from
portal-service.jar have been moved into application and framework API
modules. The table below provides details about these classes and the
modules they've moved to. Package changes and each module's group,
artifact ID, and version are listed, to facilitate configuring
dependencies.
Classes Moved from portal-service.jar to Modules
This information was generated based on comparing classes in
liferay-portal-src-6.2-ee-sp20 to classes in liferay-dxp-src-7.2.10-ga1.
Class
Package
Group ID, Artifact ID, and Version
ActionHandler
Old: com.liferay.portal.kernel.mobile.device.rulegroup.action New:
com.liferay.mobile.device.rules.action
com.liferay com.liferay.mobile.device.rules.api 4.0.4
ActionHandlerManager
Old: com.liferay.portal.kernel.mobile.device.rulegroup New:
com.liferay.mobile.device.rules.action
com.liferay com.liferay.mobile.device.rules.api 4.0.4
ActionHandlerManagerUtil
Old: com.liferay.portal.kernel.mobile.device.rulegroup New:
com.liferay.mobile.device.rules.action
com.liferay com.liferay.mobile.device.rules.api 4.0.4
ActionTypeException
Old: com.liferay.portlet.mobiledevicerules New:
com.liferay.mobile.device.rules.exception
com.liferay com.liferay.mobile.device.rules.api 4.0.4
AlternateKeywordQueryHitsProcessor
Old: com.liferay.portal.kernel.search New:
com.liferay.portal.search.internal.hits
com.liferay com.liferay.portal.search 6.0.14
ArticleContentException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleContentSizeException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleCreateDateComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleDisplayDateComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleDisplayDateException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleExpirationDateException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleIDComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleIdException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleModifiedDateComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleResourcePKComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleReviewDateComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleReviewDateException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleSmallImageNameException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleSmallImageSizeException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleTitleComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleTitleException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
ArticleVersionComparator
Old: com.liferay.portlet.journal.util.comparator New:
com.liferay.journal.util.comparator
com.liferay com.liferay.journal.api 4.2.1
ArticleVersionException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
AuditMessageProcessor
Old: com.liferay.portal.kernel.audit New:
com.liferay.portal.security.audit
com.liferay com.liferay.portal.security.audit.api 4.0.5
AutoDeleteFileInputStream
Old: com.liferay.portal.kernel.io New: com.liferay.petra.io
com.liferay com.liferay.petra.io 3.0.2
AverageStatistics
Old: com.liferay.portal.kernel.monitoring.statistics New:
com.liferay.portal.monitoring.internal.statistics
com.liferay com.liferay.portal.monitoring 7.0.7
BackgroundTaskLocalService
Old: com.liferay.portal.service New:
com.liferay.portal.background.task.service
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskLocalServiceUtil
Old: com.liferay.portal.service New:
com.liferay.portal.background.task.service
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskLocalServiceWrapper
Old: com.liferay.portal.service New:
com.liferay.portal.background.task.service
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskModel
Old: com.liferay.portal.model New:
com.liferay.portal.background.task.model
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskPersistence
Old: com.liferay.portal.service.persistence New:
com.liferay.portal.background.task.service.persistence
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskService
Old: com.liferay.portal.service New:
com.liferay.portal.background.task.service
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskServiceUtil
Old: com.liferay.portal.service New:
com.liferay.portal.background.task.service
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskServiceWrapper
Old: com.liferay.portal.service New:
com.liferay.portal.background.task.service
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskSoap
Old: com.liferay.portal.model New:
com.liferay.portal.background.task.model
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskUtil
Old: com.liferay.portal.service.persistence New:
com.liferay.portal.background.task.service.persistence
com.liferay com.liferay.portal.background.task.api 4.1.3
BackgroundTaskWrapper
Old: com.liferay.portal.model New:
com.liferay.portal.background.task.model
com.liferay com.liferay.portal.background.task.api 4.1.3
BannedUserException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
BaseCmisRepository
Old: com.liferay.portal.kernel.repository.cmis New:
com.liferay.document.library.repository.cmis
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
BaseCmisSearchQueryBuilder
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
BaseDDLExporter
Old: com.liferay.portlet.dynamicdatalists.util New:
com.liferay.dynamic.data.lists.internal.exporter
com.liferay com.liferay.dynamic.data.lists.service 3.0.12
BaseDDMDisplay
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
BaseFieldRenderer
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
BaseScriptingExecutor
Old: com.liferay.portal.kernel.scripting New:
com.liferay.portal.scripting
com.liferay com.liferay.portal.scripting.api 3.0.3
BaseStatistics
Old: com.liferay.portal.kernel.monitoring.statistics New:
com.liferay.portal.monitoring.internal.statistics
com.liferay com.liferay.portal.monitoring 7.0.7
BaseStorageAdapter
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
BlockingPortalCache
Old: com.liferay.portal.kernel.cache New: com.liferay.portal.cache
com.liferay com.liferay.portal.cache.api 2.0.1
BlogsEntry
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryFinder
Old: com.liferay.portlet.blogs.service.persistence New:
com.liferay.blogs.service.persistence
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryLocalService
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryLocalServiceUtil
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryLocalServiceWrapper
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryModel
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryPersistence
Old: com.liferay.portlet.blogs.service.persistence New:
com.liferay.blogs.service.persistence
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryService
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryServiceUtil
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryServiceWrapper
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntrySoap
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryUtil
Old: com.liferay.portlet.blogs.service.persistence New:
com.liferay.blogs.service.persistence
com.liferay com.liferay.blogs.api 5.0.5
BlogsEntryWrapper
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUser
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserFinder
Old: com.liferay.portlet.blogs.service.persistence New:
com.liferay.blogs.service.persistence
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserLocalService
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserLocalServiceUtil
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserLocalServiceWrapper
Old: com.liferay.portlet.blogs.service New: com.liferay.blogs.service
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserModel
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserPersistence
Old: com.liferay.portlet.blogs.service.persistence New:
com.liferay.blogs.service.persistence
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserSoap
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserUtil
Old: com.liferay.portlet.blogs.service.persistence New:
com.liferay.blogs.service.persistence
com.liferay com.liferay.blogs.api 5.0.5
BlogsStatsUserWrapper
Old: com.liferay.portlet.blogs.model New: com.liferay.blogs.model
com.liferay com.liferay.blogs.api 5.0.5
BookmarksEntry
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryFinder
Old: com.liferay.portlet.bookmarks.service.persistence New:
com.liferay.bookmarks.service.persistence
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryLocalService
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryLocalServiceUtil
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryLocalServiceWrapper
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryModel
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryPersistence
Old: com.liferay.portlet.bookmarks.service.persistence New:
com.liferay.bookmarks.service.persistence
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryService
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryServiceUtil
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryServiceWrapper
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntrySoap
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryUtil
Old: com.liferay.portlet.bookmarks.service.persistence New:
com.liferay.bookmarks.service.persistence
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksEntryWrapper
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolder
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderConstants
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderFinder
Old: com.liferay.portlet.bookmarks.service.persistence New:
com.liferay.bookmarks.service.persistence
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderLocalService
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderLocalServiceUtil
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderLocalServiceWrapper
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderModel
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderPersistence
Old: com.liferay.portlet.bookmarks.service.persistence New:
com.liferay.bookmarks.service.persistence
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderService
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderServiceUtil
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderServiceWrapper
Old: com.liferay.portlet.bookmarks.service New:
com.liferay.bookmarks.service
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderSoap
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderUtil
Old: com.liferay.portlet.bookmarks.service.persistence New:
com.liferay.bookmarks.service.persistence
com.liferay com.liferay.bookmarks.api 4.0.5
BookmarksFolderWrapper
Old: com.liferay.portlet.bookmarks.model New:
com.liferay.bookmarks.model
com.liferay com.liferay.bookmarks.api 4.0.5
ByteArrayReportResultContainer
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
CMISBetweenExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISConjunction
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISContainsExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISContainsNotExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISContainsValueExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISCriterion
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISDisjunction
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISFullTextConjunction
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISInFolderExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISInTreeExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISJunction
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISNotExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISParameterValueUtil
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISRepositoryHandler
Old: com.liferay.portal.kernel.repository.cmis New:
com.liferay.document.library.repository.cmis
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISRepositoryUtil
Old: com.liferay.portal.kernel.repository.cmis New:
com.liferay.document.library.repository.cmis.internal
com.liferay com.liferay.document.library.repository.cmis.impl 4.0.8
CMISSearchQueryBuilder
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISSimpleExpression
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CMISSimpleExpressionOperator
Old: com.liferay.portal.kernel.repository.cmis.search New:
com.liferay.document.library.repository.cmis.search
com.liferay com.liferay.document.library.repository.cmis.api 3.0.7
CharPool
Old: com.liferay.portal.kernel.util New: com.liferay.petra.string
com.liferay com.liferay.petra.string 3.0.1
CharsetDecoderUtil
Old: com.liferay.portal.kernel.nio.charset New: com.liferay.petra.nio
com.liferay com.liferay.petra.nio 3.0.1
CharsetEncoderUtil
Old: com.liferay.portal.kernel.nio.charset New: com.liferay.petra.nio
com.liferay com.liferay.petra.nio 3.0.1
ClassLoaderPool
Old: com.liferay.portal.kernel.util New: com.liferay.petra.lang
com.liferay com.liferay.petra.lang 3.0.1
ClassPathUtil
Old: com.liferay.portal.kernel.process New: com.liferay.petra.process
com.liferay com.liferay.petra.process 3.0.4
ClassResolverUtil
Old: com.liferay.portal.kernel.util New: com.liferay.petra.lang
com.liferay com.liferay.petra.lang 3.0.1
CollatedSpellCheckHitsProcessor
Old: com.liferay.portal.kernel.search New:
com.liferay.portal.search.internal.hits
com.liferay com.liferay.portal.search 6.0.14
CompoundSessionIdServletRequest
Old: com.liferay.portal.kernel.servlet.filters.compoundsessionid New:
com.liferay.portal.compound.session.id.internal
com.liferay com.liferay.portal.compound.session.id 4.0.5
Condition
Old: com.liferay.portlet.dynamicdatamapping.storage.query New:
com.liferay.adaptive.media.image.media.query
com.liferay com.liferay.adaptive.media.image.api 3.0.3
ConsumerOutputProcessor
Old: com.liferay.portal.kernel.process New: com.liferay.petra.process
com.liferay com.liferay.petra.process 3.0.4
ContactConverterKeys
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap
com.liferay com.liferay.portal.security.ldap.api 2.0.8
ContentException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
ContentNameException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
ContextClassloaderReportDesignRetriever
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
CountStatistics
Old: com.liferay.portal.kernel.monitoring.statistics New:
com.liferay.portal.monitoring.internal.statistics
com.liferay com.liferay.portal.monitoring 7.0.7
DDL
Old: com.liferay.portlet.dynamicdatalists.util New:
com.liferay.dynamic.data.lists.util
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLExporter
Old: com.liferay.portlet.dynamicdatalists.util New:
com.liferay.dynamic.data.lists.exporter
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLExporterFactory
Old: com.liferay.portlet.dynamicdatalists.util New:
com.liferay.dynamic.data.lists.exporter
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecord
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordConstants
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordFinder
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordLocalService
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordLocalServiceUtil
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordLocalServiceWrapper
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordModel
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordPersistence
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordService
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordServiceUtil
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordServiceWrapper
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSet
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetConstants
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetFinder
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetLocalService
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetLocalServiceUtil
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetLocalServiceWrapper
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetModel
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetPersistence
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetService
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetServiceUtil
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetServiceWrapper
Old: com.liferay.portlet.dynamicdatalists.service New:
com.liferay.dynamic.data.lists.service
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetSoap
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetUtil
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSetWrapper
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordSoap
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordUtil
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordVersion
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordVersionModel
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordVersionPersistence
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordVersionSoap
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordVersionUtil
Old: com.liferay.portlet.dynamicdatalists.service.persistence New:
com.liferay.dynamic.data.lists.service.persistence
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordVersionVersionComparator
Old: com.liferay.portlet.dynamicdatalists.util.comparator New:
com.liferay.dynamic.data.lists.util.comparator
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordVersionWrapper
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDLRecordWrapper
Old: com.liferay.portlet.dynamicdatalists.model New:
com.liferay.dynamic.data.lists.model
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
DDM
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContent
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentLocalService
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentLocalServiceUtil
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentLocalServiceWrapper
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentModel
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentPersistence
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentSoap
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentUtil
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMContentWrapper
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMDisplay
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMDisplayRegistry
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMIndexer
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMIndexerUtil
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.asset.list.internal.dynamic.data.mapping.util
com.liferay com.liferay.asset.list.service 1.0.11
DDMStorageLink
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkLocalService
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkLocalServiceUtil
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkLocalServiceWrapper
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkModel
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkPersistence
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkSoap
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkUtil
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStorageLinkWrapper
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureConstants
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureFinder
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkLocalService
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkLocalServiceUtil
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkLocalServiceWrapper
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkModel
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkPersistence
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkSoap
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkUtil
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLinkWrapper
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLocalService
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLocalServiceUtil
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureLocalServiceWrapper
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureModel
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructurePersistence
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureService
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureServiceUtil
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureServiceWrapper
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureSoap
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureUtil
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMStructureWrapper
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateConstants
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateFinder
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateHelper
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateLocalService
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateLocalServiceUtil
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateLocalServiceWrapper
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateModel
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplatePersistence
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateService
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateServiceUtil
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateServiceWrapper
Old: com.liferay.portlet.dynamicdatamapping.service New:
com.liferay.dynamic.data.mapping.service
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateSoap
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateUtil
Old: com.liferay.portlet.dynamicdatamapping.service.persistence New:
com.liferay.dynamic.data.mapping.service.persistence
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMTemplateWrapper
Old: com.liferay.portlet.dynamicdatamapping.model New:
com.liferay.dynamic.data.mapping.model
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMUtil
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DDMXML
Old: com.liferay.portlet.dynamicdatamapping.util New:
com.liferay.dynamic.data.mapping.util
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
DLContent
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.content.model
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentDataBlobModel
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.content.model
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentLocalService
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.content.service
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentLocalServiceUtil
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.content.service
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentLocalServiceWrapper
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.content.service
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentModel
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.content.model
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentPersistence
Old: com.liferay.portlet.documentlibrary.service.persistence New:
com.liferay.document.library.content.service.persistence
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentSoap
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.content.model
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentUtil
Old: com.liferay.portlet.documentlibrary.service.persistence New:
com.liferay.document.library.content.service.persistence
com.liferay com.liferay.document.library.content.api 2.0.3
DLContentVersionComparator
Old: com.liferay.portlet.documentlibrary.util.comparator New:
com.liferay.document.library.content.service.util.comparator
com.liferay com.liferay.document.library.content.service 2.0.3
DLContentWrapper
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.content.model
com.liferay com.liferay.document.library.content.api 2.0.3
DLFileRank
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.file.rank.model
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankFinder
Old: com.liferay.portlet.documentlibrary.service.persistence New:
com.liferay.document.library.file.rank.service.persistence
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankLocalService
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.file.rank.service
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankLocalServiceUtil
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.file.rank.service
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankLocalServiceWrapper
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.file.rank.service
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankModel
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.file.rank.model
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankPersistence
Old: com.liferay.portlet.documentlibrary.service.persistence New:
com.liferay.document.library.file.rank.service.persistence
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankSoap
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.file.rank.model
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankUtil
Old: com.liferay.portlet.documentlibrary.service.persistence New:
com.liferay.document.library.file.rank.service.persistence
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLFileRankWrapper
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.file.rank.model
com.liferay com.liferay.document.library.file.rank.api 2.0.3
DLSyncConstants
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.sync.constants
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEvent
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.sync.model
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventLocalService
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.sync.service
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventLocalServiceUtil
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.sync.service
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventLocalServiceWrapper
Old: com.liferay.portlet.documentlibrary.service New:
com.liferay.document.library.sync.service
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventModel
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.sync.model
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventPersistence
Old: com.liferay.portlet.documentlibrary.service.persistence New:
com.liferay.document.library.sync.service.persistence
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventSoap
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.sync.model
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventUtil
Old: com.liferay.portlet.documentlibrary.service.persistence New:
com.liferay.document.library.sync.service.persistence
com.liferay com.liferay.document.library.sync.api 2.0.3
DLSyncEventWrapper
Old: com.liferay.portlet.documentlibrary.model New:
com.liferay.document.library.sync.model
com.liferay com.liferay.document.library.sync.api 2.0.3
Database
Old: com.liferay.portal.kernel.util New:
com.liferay.portal.tools.db.upgrade.client
com.liferay com.liferay.portal.tools.db.upgrade.client 3.0.0
DefaultAttributesTransformer
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap.internal
com.liferay com.liferay.portal.security.ldap.impl 2.0.5
DefaultMessageBus
Old: com.liferay.portal.kernel.messaging New:
com.liferay.portal.messaging.internal
com.liferay com.liferay.portal.messaging 6.0.5
DefaultSingleDestinationMessageSender
Old: com.liferay.portal.kernel.messaging.sender New:
com.liferay.portal.messaging.internal.sender
com.liferay com.liferay.portal.messaging 6.0.5
DefaultSingleDestinationSynchronousMessageSender
Old: com.liferay.portal.kernel.messaging.sender New:
com.liferay.portal.messaging.internal.sender
com.liferay com.liferay.portal.messaging 6.0.5
DefaultSynchronousMessageSender
Old: com.liferay.portal.kernel.messaging.sender New:
com.liferay.portal.messaging.internal.sender
com.liferay com.liferay.portal.messaging 6.0.5
DeleteFileFinalizeAction
Old: com.liferay.portal.kernel.memory New: com.liferay.petra.memory
com.liferay com.liferay.petra.memory 3.0.1
DestinationStatisticsManager
Old: com.liferay.portal.kernel.messaging.jmx New:
com.liferay.portal.messaging.internal.jmx
com.liferay com.liferay.portal.messaging 6.0.5
DestinationStatisticsManagerMBean
Old: com.liferay.portal.kernel.messaging.jmx New:
com.liferay.portal.messaging.internal.jmx
com.liferay com.liferay.portal.messaging 6.0.5
DirectSynchronousMessageSender
Old: com.liferay.portal.kernel.messaging.sender New:
com.liferay.portal.messaging.internal.sender
com.liferay com.liferay.portal.messaging 6.0.5
Dummy
Old: com.liferay.portal.model New:
com.liferay.exportimport.test.util.model
com.liferay com.liferay.exportimport.test.util 2.0.6
DummyContext
Old: com.liferay.portal.kernel.ldap New:
com.liferay.portal.security.ldap.dummy
com.liferay com.liferay.portal.security.ldap.api 2.0.8
DummyDirContext
Old: com.liferay.portal.kernel.ldap New:
com.liferay.portal.security.ldap.dummy
com.liferay com.liferay.portal.security.ldap.api 2.0.8
DummyFinalizeAction
Old: com.liferay.portal.kernel.memory New: com.liferay.petra.memory
com.liferay com.liferay.petra.memory 3.0.1
DuplicateArticleIdException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
DuplicateFeedIdException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
DuplicateLDAPServerNameException
Old: com.liferay.portal.kernel.ldap New:
com.liferay.portal.security.ldap
com.liferay com.liferay.portal.security.ldap.api 2.0.8
DuplicateNodeNameException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
DuplicatePageException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
DuplicateRuleGroupInstanceException
Old: com.liferay.portlet.mobiledevicerules New:
com.liferay.mobile.device.rules.exception
com.liferay com.liferay.mobile.device.rules.api 4.0.4
DuplicateVoteException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
EntryDisplayDateComparator
Old: com.liferay.portlet.blogs.util.comparator New:
com.liferay.blogs.util.comparator
com.liferay com.liferay.blogs.api 5.0.5
EntryModifiedDateComparator
Old: com.liferay.portlet.bookmarks.util.comparator New:
com.liferay.bookmarks.util.comparator
com.liferay com.liferay.bookmarks.api 4.0.5
EntryNameComparator
Old: com.liferay.portlet.bookmarks.util.comparator New:
com.liferay.bookmarks.util.comparator
com.liferay com.liferay.bookmarks.api 4.0.5
EntryPriorityComparator
Old: com.liferay.portlet.bookmarks.util.comparator New:
com.liferay.bookmarks.util.comparator
com.liferay com.liferay.bookmarks.api 4.0.5
EntrySmallImageNameException
Old: com.liferay.portlet.blogs New: com.liferay.blogs.exception
com.liferay com.liferay.blogs.api 5.0.5
EntryURLComparator
Old: com.liferay.portlet.bookmarks.util.comparator New:
com.liferay.bookmarks.util.comparator
com.liferay com.liferay.bookmarks.api 4.0.5
EntryVisitsComparator
Old: com.liferay.portlet.bookmarks.util.comparator New:
com.liferay.bookmarks.util.comparator
com.liferay com.liferay.bookmarks.api 4.0.5
EqualityWeakReference
Old: com.liferay.portal.kernel.memory New: com.liferay.petra.memory
com.liferay com.liferay.petra.memory 3.0.1
Fact
Old: com.liferay.portal.kernel.bi.rules New:
com.liferay.portal.rules.engine
com.liferay com.liferay.portal.rules.engine.api 4.0.4
FeedContentFieldException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
FeedIdException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
FeedNameException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
FeedTargetLayoutFriendlyUrlException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
FeedTargetPortletIdException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
FieldConstants
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
FieldRenderer
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
FieldRendererFactory
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
Fields
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
FileRankCreateDateComparator
Old: com.liferay.portlet.documentlibrary.util.comparator New:
com.liferay.document.library.file.rank.util.comparator
com.liferay com.liferay.document.library.file.rank.service 2.0.6
FinalizeAction
Old: com.liferay.portal.kernel.memory New: com.liferay.petra.memory
com.liferay com.liferay.petra.memory 3.0.1
FinalizeManager
Old: com.liferay.portal.kernel.memory New: com.liferay.petra.memory
com.liferay com.liferay.petra.memory 3.0.1
FlagsEntryService
Old: com.liferay.portlet.flags.service New: com.liferay.flags.service
com.liferay com.liferay.flags.api 4.0.6
FlagsEntryServiceUtil
Old: com.liferay.portlet.flags.service New: com.liferay.flags.service
com.liferay com.liferay.flags.api 4.0.6
FlagsEntryServiceWrapper
Old: com.liferay.portlet.flags.service New: com.liferay.flags.service
com.liferay com.liferay.flags.api 4.0.6
FlagsRequest
Old: com.liferay.portlet.flags.messaging New:
com.liferay.flags.internal.messaging
com.liferay com.liferay.flags.service 4.0.2
GroupConverterKeys
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap
com.liferay com.liferay.portal.security.ldap.api 2.0.8
ImportFilesException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
JournalArticle
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleConstants
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleDisplay
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleFinder
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalArticleLocalService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleLocalServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleLocalServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleModel
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticlePersistence
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResource
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourceLocalService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourceLocalServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourceLocalServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourceModel
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourcePersistence
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourceSoap
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourceUtil
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalArticleResourceWrapper
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalArticleSoap
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalArticleUtil
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalArticleWrapper
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalContent
Old: com.liferay.portlet.journalcontent.util New:
com.liferay.journal.util
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearch
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchLocalService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchLocalServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchLocalServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchModel
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchPersistence
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchSoap
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchUtil
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalContentSearchWrapper
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalConverter
Old: com.liferay.portlet.journal.util New: com.liferay.journal.util
com.liferay com.liferay.journal.api 4.2.1
JournalFeed
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFeedConstants
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFeedFinder
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalFeedLocalService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFeedLocalServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFeedLocalServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFeedModel
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFeedPersistence
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalFeedService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFeedServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFeedServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFeedSoap
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFeedUtil
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalFeedWrapper
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFolder
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFolderFinder
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalFolderLocalService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFolderLocalServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFolderLocalServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFolderModel
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFolderPersistence
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalFolderService
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFolderServiceUtil
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFolderServiceWrapper
Old: com.liferay.portlet.journal.service New:
com.liferay.journal.service
com.liferay com.liferay.journal.api 4.2.1
JournalFolderSoap
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalFolderUtil
Old: com.liferay.portlet.journal.service.persistence New:
com.liferay.journal.service.persistence
com.liferay com.liferay.journal.api 4.2.1
JournalFolderWrapper
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalSearchConstants
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
JournalStructureConstants
Old: com.liferay.portlet.journal.model New: com.liferay.journal.model
com.liferay com.liferay.journal.api 4.2.1
LDAPFilterException
Old: com.liferay.portal.kernel.ldap New:
com.liferay.portal.security.ldap.validator
com.liferay com.liferay.portal.security.ldap.api 2.0.8
LDAPGroup
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap.exportimport
com.liferay com.liferay.portal.security.ldap.api 2.0.8
LDAPServerNameException
Old: com.liferay.portal.kernel.ldap New:
com.liferay.portal.security.ldap
com.liferay com.liferay.portal.security.ldap.api 2.0.8
LDAPToPortalConverter
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap.exportimport
com.liferay com.liferay.portal.security.ldap.api 2.0.8
LDAPUser
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap.exportimport
com.liferay com.liferay.portal.security.ldap.api 2.0.8
LDAPUtil
Old: com.liferay.portal.kernel.ldap New:
com.liferay.portal.security.ldap.util
com.liferay com.liferay.portal.security.ldap.api 2.0.8
LockLocalService
Old: com.liferay.portal.service New: com.liferay.portal.lock.service
com.liferay com.liferay.portal.lock.api 4.1.1
LockLocalServiceUtil
Old: com.liferay.portal.service New: com.liferay.portal.lock.service
com.liferay com.liferay.portal.lock.api 4.1.1
LockLocalServiceWrapper
Old: com.liferay.portal.service New: com.liferay.portal.lock.service
com.liferay com.liferay.portal.lock.api 4.1.1
LockModel
Old: com.liferay.portal.model New: com.liferay.portal.lock.model
com.liferay com.liferay.portal.lock.api 4.1.1
LockPersistence
Old: com.liferay.portal.service.persistence New:
com.liferay.portal.lock.service.persistence
com.liferay com.liferay.portal.lock.api 4.1.1
LockSoap
Old: com.liferay.portal.model New: com.liferay.portal.lock.model
com.liferay com.liferay.portal.lock.api 4.1.1
LockUtil
Old: com.liferay.portal.service.persistence New:
com.liferay.portal.lock.service.persistence
com.liferay com.liferay.portal.lock.api 4.1.1
LockWrapper
Old: com.liferay.portal.model New: com.liferay.portal.lock.model
com.liferay com.liferay.portal.lock.api 4.1.1
LockedThreadException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
LoggingOutputProcessor
Old: com.liferay.portal.kernel.process New: com.liferay.petra.process
com.liferay com.liferay.petra.process 3.0.4
MBBan
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBBanLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBBanLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBBanLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBBanModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBBanPersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBBanService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBBanServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBBanServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBBanSoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBBanUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBBanWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBCategory
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryConstants
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.constants
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryDisplay
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.web.internal.display
com.liferay com.liferay.message.boards.web 3.0.17
MBCategoryFinder
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryPersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBCategorySoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBCategoryWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussion
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionPersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionSoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBDiscussionWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingList
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListPersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListSoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBMailingListWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMessage
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageConstants
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.constants
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageDisplay
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageFinder
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMessagePersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageSoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBMessageWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUser
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserPersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserSoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBStatsUserWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThread
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadConstants
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.constants
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFinder
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlag
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagPersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagSoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadFlagWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadLocalService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadLocalServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadLocalServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadModel
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadPersistence
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadService
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadServiceUtil
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadServiceWrapper
Old: com.liferay.portlet.messageboards.service New:
com.liferay.message.boards.service
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadSoap
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadUtil
Old: com.liferay.portlet.messageboards.service.persistence New:
com.liferay.message.boards.service.persistence
com.liferay com.liferay.message.boards.api 5.1.4
MBThreadWrapper
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBTreeWalker
Old: com.liferay.portlet.messageboards.model New:
com.liferay.message.boards.model
com.liferay com.liferay.message.boards.api 5.1.4
MBeanRegistry
Old: com.liferay.portal.kernel.jmx New: com.liferay.portal.jmx
com.liferay com.liferay.portal.jmx.api 3.0.1
MDRAction
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionLocalService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionLocalServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionLocalServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionModel
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionPersistence
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionSoap
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionUtil
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRActionWrapper
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRPermission
Old: com.liferay.portlet.mobiledevicerules.service.permission New:
com.liferay.mobile.device.rules.web.internal.security.permission.resource
com.liferay com.liferay.mobile.device.rules.web 3.0.6
MDRRule
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroup
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupFinder
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceLocalService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceLocalServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceLocalServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceModel
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstancePermission
Old: com.liferay.portlet.mobiledevicerules.service.permission New:
com.liferay.mobile.device.rules.web.internal.security.permission.resource
com.liferay com.liferay.mobile.device.rules.web 3.0.6
MDRRuleGroupInstancePersistence
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceSoap
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceUtil
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupInstanceWrapper
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupLocalService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupLocalServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupLocalServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupModel
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupPermission
Old: com.liferay.portlet.mobiledevicerules.service.permission New:
com.liferay.mobile.device.rules.web.internal.security.permission.resource
com.liferay com.liferay.mobile.device.rules.web 3.0.6
MDRRuleGroupPersistence
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupSoap
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupUtil
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleGroupWrapper
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleLocalService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleLocalServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleLocalServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleModel
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRulePersistence
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleService
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleServiceUtil
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleServiceWrapper
Old: com.liferay.portlet.mobiledevicerules.service New:
com.liferay.mobile.device.rules.service
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleSoap
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleUtil
Old: com.liferay.portlet.mobiledevicerules.service.persistence New:
com.liferay.mobile.device.rules.service.persistence
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MDRRuleWrapper
Old: com.liferay.portlet.mobiledevicerules.model New:
com.liferay.mobile.device.rules.model
com.liferay com.liferay.mobile.device.rules.api 4.0.4
MailingListEmailAddressException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MailingListInServerNameException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MailingListInUserNameException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MailingListOutEmailAddressException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MailingListOutServerNameException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MailingListOutUserNameException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MemoryReportDesignRetriever
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
MessageBodyException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MessageBusManager
Old: com.liferay.portal.kernel.messaging.jmx New:
com.liferay.portal.messaging.internal.jmx
com.liferay com.liferay.portal.messaging 6.0.5
MessageBusManagerMBean
Old: com.liferay.portal.kernel.messaging.jmx New:
com.liferay.portal.messaging.internal.jmx
com.liferay com.liferay.portal.messaging 6.0.5
MessageCreateDateComparator
Old: com.liferay.portlet.messageboards.util.comparator New:
com.liferay.message.boards.util.comparator
com.liferay com.liferay.message.boards.api 5.1.4
MessageSubjectException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
MessageThreadComparator
Old: com.liferay.portlet.messageboards.util.comparator New:
com.liferay.message.boards.util.comparator
com.liferay com.liferay.message.boards.api 5.1.4
Modifications
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap.exportimport
com.liferay com.liferay.portal.security.ldap.api 2.0.8
NoSuchArticleException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
NoSuchArticleImageException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
NoSuchArticleResourceException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
NoSuchBanException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
NoSuchChoiceException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
NoSuchContentException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
NoSuchContentSearchException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
NoSuchDiscussionException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
NoSuchFeedException
Old: com.liferay.portlet.journal New: com.liferay.journal.exception
com.liferay com.liferay.journal.api 4.2.1
NoSuchFileRankException
Old: com.liferay.portlet.documentlibrary New:
com.liferay.document.library.file.rank.exception
com.liferay com.liferay.document.library.file.rank.api 2.0.3
NoSuchMailingListException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
NoSuchNodeException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
NoSuchPageException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
NoSuchPageResourceException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
NoSuchQuestionException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
NoSuchRecordException
Old: com.liferay.portlet.dynamicdatalists New:
com.liferay.dynamic.data.lists.exception
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
NoSuchRecordSetException
Old: com.liferay.portlet.dynamicdatalists New:
com.liferay.dynamic.data.lists.exception
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
NoSuchRecordVersionException
Old: com.liferay.portlet.dynamicdatalists New:
com.liferay.dynamic.data.lists.exception
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
NoSuchRuleException
Old: com.liferay.portlet.mobiledevicerules New:
com.liferay.mobile.device.rules.exception
com.liferay com.liferay.mobile.device.rules.api 4.0.4
NoSuchRuleGroupException
Old: com.liferay.portlet.mobiledevicerules New:
com.liferay.mobile.device.rules.exception
com.liferay com.liferay.mobile.device.rules.api 4.0.4
NoSuchRuleGroupInstanceException
Old: com.liferay.portlet.mobiledevicerules New:
com.liferay.mobile.device.rules.exception
com.liferay com.liferay.mobile.device.rules.api 4.0.4
NoSuchStatsUserException
Old: com.liferay.portlet.blogs New: com.liferay.blogs.exception
com.liferay com.liferay.blogs.api 5.0.5
NoSuchStorageLinkException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
NoSuchStructureLinkException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
NoSuchTemplateException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
NoSuchThreadException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
NoSuchThreadFlagException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
NoSuchVoteException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
NodeNameException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
OutputProcessor
Old: com.liferay.portal.kernel.process New: com.liferay.petra.process
com.liferay com.liferay.petra.process 3.0.4
PageContentException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
PageCreateDateComparator
Old: com.liferay.portlet.wiki.util.comparator New:
com.liferay.wiki.util.comparator
com.liferay com.liferay.wiki.api 4.0.7
PageTitleComparator
Old: com.liferay.portlet.wiki.util.comparator New:
com.liferay.wiki.util.comparator
com.liferay com.liferay.wiki.api 4.0.7
PageTitleException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
PageVersionComparator
Old: com.liferay.portlet.wiki.util.comparator New:
com.liferay.wiki.util.comparator
com.liferay com.liferay.wiki.api 4.0.7
PageVersionException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
PollsChoice
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceLocalService
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceLocalServiceUtil
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceLocalServiceWrapper
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceModel
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsChoicePersistence
Old: com.liferay.portlet.polls.service.persistence New:
com.liferay.polls.service.persistence
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceService
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceServiceUtil
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceServiceWrapper
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceSoap
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceUtil
Old: com.liferay.portlet.polls.service.persistence New:
com.liferay.polls.service.persistence
com.liferay com.liferay.polls.api 6.0.3
PollsChoiceWrapper
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsQuestion
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionLocalService
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionLocalServiceUtil
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionLocalServiceWrapper
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionModel
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionPersistence
Old: com.liferay.portlet.polls.service.persistence New:
com.liferay.polls.service.persistence
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionService
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionServiceUtil
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionServiceWrapper
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionSoap
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionUtil
Old: com.liferay.portlet.polls.service.persistence New:
com.liferay.polls.service.persistence
com.liferay com.liferay.polls.api 6.0.3
PollsQuestionWrapper
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsVote
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsVoteLocalService
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsVoteLocalServiceUtil
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsVoteLocalServiceWrapper
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsVoteModel
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsVotePersistence
Old: com.liferay.portlet.polls.service.persistence New:
com.liferay.polls.service.persistence
com.liferay com.liferay.polls.api 6.0.3
PollsVoteService
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsVoteServiceUtil
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsVoteServiceWrapper
Old: com.liferay.portlet.polls.service New: com.liferay.polls.service
com.liferay com.liferay.polls.api 6.0.3
PollsVoteSoap
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PollsVoteUtil
Old: com.liferay.portlet.polls.service.persistence New:
com.liferay.polls.service.persistence
com.liferay com.liferay.polls.api 6.0.3
PollsVoteWrapper
Old: com.liferay.portlet.polls.model New: com.liferay.polls.model
com.liferay com.liferay.polls.api 6.0.3
PoolAction
Old: com.liferay.portal.kernel.memory New: com.liferay.petra.memory
com.liferay com.liferay.petra.memory 3.0.1
PortalCacheClusterChannel
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal.cluster.link
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalCacheClusterChannelFactory
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal.cluster.link
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalCacheClusterChannelSelector
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal.cluster.link
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalCacheClusterEvent
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalCacheClusterEventCoalesceComparator
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalCacheClusterEventType
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalCacheClusterException
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalCacheClusterLink
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal.cluster.link
com.liferay com.liferay.portal.cache.multiple 3.0.6
PortalExecutorFactory
Old: com.liferay.portal.kernel.executor New:
com.liferay.portal.executor.internal
com.liferay com.liferay.portal.executor 4.0.2
PortalToLDAPConverter
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap.exportimport
com.liferay com.liferay.portal.security.ldap.api 2.0.8
PortletDisplayTemplate
Old: com.liferay.portlet.portletdisplaytemplate.util New:
com.liferay.portlet.display.template
com.liferay com.liferay.portlet.display.template.api 2.0.2
PortletDisplayTemplateConstants
Old: com.liferay.portlet.portletdisplaytemplate.util New:
com.liferay.portlet.display.template.constants
com.liferay com.liferay.portlet.display.template.api 2.0.2
PortletDisplayTemplateUtil
Old: com.liferay.portlet.portletdisplaytemplate.util New:
com.liferay.roles.admin.web.internal.util
com.liferay com.liferay.roles.admin.web 3.0.6
PortletDisplayTemplateUtil
Old: com.liferay.portlet.portletdisplaytemplate.util New:
com.liferay.roles.admin.web.internal.util
com.liferay com.liferay.roles.admin.web 3.0.6
PortletDisplayTemplateUtil
Old: com.liferay.portlet.portletdisplaytemplate.util New:
com.liferay.roles.admin.web.internal.util
com.liferay com.liferay.roles.admin.web 3.0.6
ProcessUtil
Old: com.liferay.portal.kernel.process New: com.liferay.petra.process
com.liferay com.liferay.petra.process 3.0.4
QueryIndexingHitsProcessor
Old: com.liferay.portal.kernel.search New:
com.liferay.portal.search.internal.hits
com.liferay com.liferay.portal.search 6.0.14
QuerySuggestionHitsProcessor
Old: com.liferay.portal.kernel.search New:
com.liferay.portal.search.internal.hits
com.liferay com.liferay.portal.search 6.0.14
QueryType
Old: com.liferay.portal.kernel.bi.rules New:
com.liferay.portal.rules.engine
com.liferay com.liferay.portal.rules.engine.api 4.0.4
QuestionChoiceException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
QuestionDescriptionException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
QuestionExpirationDateException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
QuestionExpiredException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
QuestionTitleException
Old: com.liferay.portlet.polls New: com.liferay.polls.exception
com.liferay com.liferay.polls.api 6.0.3
RecordSetDDMStructureIdException
Old: com.liferay.portlet.dynamicdatalists New:
com.liferay.dynamic.data.lists.exception
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
RecordSetDuplicateRecordSetKeyException
Old: com.liferay.portlet.dynamicdatalists New:
com.liferay.dynamic.data.lists.exception
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
RecordSetNameException
Old: com.liferay.portlet.dynamicdatalists New:
com.liferay.dynamic.data.lists.exception
com.liferay com.liferay.dynamic.data.lists.api 4.0.5
RegistryAwareMBeanServer
Old: com.liferay.portal.kernel.jmx New: com.liferay.portal.jmx.internal
com.liferay com.liferay.portal.jmx 6.0.2
ReportCompilerRequestMessageListener
Old: com.liferay.portal.kernel.bi.reporting.messaging New:
com.liferay.portal.reports.engine.messaging
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportDataSourceType
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportDesignRetriever
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportEngine
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportExportException
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportFormat
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportFormatExporter
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportFormatExporterRegistry
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportGenerationException
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportRequest
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportRequestContext
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportRequestMessageListener
Old: com.liferay.portal.kernel.bi.reporting.messaging New:
com.liferay.portal.reports.engine.messaging
com.liferay com.liferay.portal.reports.engine.api 5.0.1
ReportResultContainer
Old: com.liferay.portal.kernel.bi.reporting New:
com.liferay.portal.reports.engine
com.liferay com.liferay.portal.reports.engine.api 5.0.1
RequestStatistics
Old: com.liferay.portal.kernel.monitoring.statistics New:
com.liferay.portal.monitoring.internal.statistics
com.liferay com.liferay.portal.monitoring 7.0.7
RequiredMessageException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
RequiredNodeException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
RequiredTemplateException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
RequiredTemplateException
Old: com.liferay.portlet.journal New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
RuleGroupInstancePriorityComparator
Old: com.liferay.portlet.mobiledevicerules.util New:
com.liferay.mobile.device.rules.util.comparator
com.liferay com.liferay.mobile.device.rules.api 4.0.4
RuleGroupProcessor
Old: com.liferay.portal.kernel.mobile.device.rulegroup New:
com.liferay.mobile.device.rules.rule
com.liferay com.liferay.mobile.device.rules.api 4.0.4
RuleGroupProcessorUtil
Old: com.liferay.portal.kernel.mobile.device.rulegroup New:
com.liferay.mobile.device.rules.rule
com.liferay com.liferay.mobile.device.rules.api 4.0.4
RuleHandler
Old: com.liferay.portal.kernel.mobile.device.rulegroup.rule New:
com.liferay.mobile.device.rules.rule
com.liferay com.liferay.mobile.device.rules.api 4.0.4
RulesEngine
Old: com.liferay.portal.kernel.bi.rules New:
com.liferay.portal.rules.engine
com.liferay com.liferay.portal.rules.engine.api 4.0.4
RulesEngineException
Old: com.liferay.portal.kernel.bi.rules New:
com.liferay.portal.rules.engine
com.liferay com.liferay.portal.rules.engine.api 4.0.4
RulesEngineUtil
Old: com.liferay.portal.kernel.bi.rules New:
com.liferay.portal.rules.engine
com.liferay com.liferay.portal.rules.engine.api 4.0.4
RulesLanguage
Old: com.liferay.portal.kernel.bi.rules New:
com.liferay.portal.rules.engine
com.liferay com.liferay.portal.rules.engine.api 4.0.4
RulesResourceRetriever
Old: com.liferay.portal.kernel.bi.rules New:
com.liferay.portal.rules.engine
com.liferay com.liferay.portal.rules.engine.api 4.0.4
SearchUtil
Old: com.liferay.portal.kernel.search.util New:
com.liferay.portal.vulcan.util
com.liferay com.liferay.portal.vulcan.api 3.2.2
SearchUtil
Old: com.liferay.portal.kernel.search.util New:
com.liferay.portal.vulcan.util
com.liferay com.liferay.portal.vulcan.api 3.2.2
ServletContextReportDesignRetriever
Old: com.liferay.portal.kernel.bi.reporting.servlet New:
com.liferay.portal.reports.engine.servlet
com.liferay com.liferay.portal.reports.engine.api 5.0.1
SoftReferencePool
Old: com.liferay.portal.kernel.memory New: com.liferay.petra.memory
com.liferay com.liferay.petra.memory 3.0.1
SortFactoryImpl
Old: com.liferay.portal.kernel.search New:
com.liferay.portal.search.internal
com.liferay com.liferay.portal.search 6.0.14
SplitThreadException
Old: com.liferay.portlet.messageboards New:
com.liferay.message.boards.exception
com.liferay com.liferay.message.boards.api 5.1.4
Statistics
Old: com.liferay.portal.kernel.monitoring.statistics New:
com.liferay.portal.monitoring.internal.statistics
com.liferay com.liferay.portal.monitoring 7.0.7
StatsUserLastPostDateComparator
Old: com.liferay.portlet.blogs.util.comparator New:
com.liferay.blogs.util.comparator
com.liferay com.liferay.blogs.api 5.0.5
StorageAdapter
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StorageEngine
Old: com.liferay.portlet.dynamicdatamapping.storage New:
com.liferay.dynamic.data.mapping.storage
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StorageException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StorageFieldNameException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StringQueryImpl
Old: com.liferay.portal.kernel.search New:
com.liferay.portal.search.internal.query
com.liferay com.liferay.portal.search 6.0.14
StructureDuplicateStructureKeyException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StructureFieldException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StructureIdComparator
Old: com.liferay.portlet.dynamicdatamapping.util.comparator New:
com.liferay.dynamic.data.mapping.util.comparator
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StructureModifiedDateComparator
Old: com.liferay.portlet.dynamicdatamapping.util.comparator New:
com.liferay.dynamic.data.mapping.util.comparator
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
StructureStructureKeyComparator
Old: com.liferay.portlet.dynamicdatamapping.util.comparator New:
com.liferay.dynamic.data.mapping.util.comparator
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
SummaryStatistics
Old: com.liferay.portal.kernel.monitoring.statistics New:
com.liferay.portal.monitoring.internal.statistics
com.liferay com.liferay.portal.monitoring 7.0.7
SynchronousMessageListener
Old: com.liferay.portal.kernel.messaging.sender New:
com.liferay.portal.messaging.internal.sender
com.liferay com.liferay.portal.messaging 6.0.5
TemplateDuplicateTemplateKeyException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateIdComparator
Old: com.liferay.portlet.dynamicdatamapping.util.comparator New:
com.liferay.dynamic.data.mapping.util.comparator
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateModifiedDateComparator
Old: com.liferay.portlet.dynamicdatamapping.util.comparator New:
com.liferay.dynamic.data.mapping.util.comparator
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateNameException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateNameException
Old: com.liferay.portlet.journal New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateScriptException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateSmallImageNameException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateSmallImageNameException
Old: com.liferay.portlet.journal New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateSmallImageSizeException
Old: com.liferay.portlet.dynamicdatamapping New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
TemplateSmallImageSizeException
Old: com.liferay.portlet.journal New:
com.liferay.dynamic.data.mapping.exception
com.liferay com.liferay.dynamic.data.mapping.api 5.2.1
ThreadLastPostDateComparator
Old: com.liferay.portlet.messageboards.util.comparator New:
com.liferay.message.boards.util.comparator
com.liferay com.liferay.message.boards.api 5.1.4
UniformPortalCacheClusterChannelSelector
Old: com.liferay.portal.kernel.cache.cluster New:
com.liferay.portal.cache.multiple.internal.cluster.link
com.liferay com.liferay.portal.cache.multiple 3.0.6
UnknownRuleHandlerException
Old: com.liferay.portal.kernel.mobile.device.rulegroup.rule New:
com.liferay.mobile.device.rules.rule
com.liferay com.liferay.mobile.device.rules.api 4.0.4
UserConverterKeys
Old: com.liferay.portal.security.ldap New:
com.liferay.portal.security.ldap
com.liferay com.liferay.portal.security.ldap.api 2.0.8
WikiFormatException
Old: com.liferay.portlet.wiki New: com.liferay.wiki.exception
com.liferay com.liferay.wiki.api 4.0.7
WikiNode
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeLocalService
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeLocalServiceUtil
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeLocalServiceWrapper
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeModel
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiNodePersistence
Old: com.liferay.portlet.wiki.service.persistence New:
com.liferay.wiki.service.persistence
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeService
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeServiceUtil
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeServiceWrapper
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeSoap
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeUtil
Old: com.liferay.portlet.wiki.service.persistence New:
com.liferay.wiki.service.persistence
com.liferay com.liferay.wiki.api 4.0.7
WikiNodeWrapper
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPage
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageConstants
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageDisplay
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageFinder
Old: com.liferay.portlet.wiki.service.persistence New:
com.liferay.wiki.service.persistence
com.liferay com.liferay.wiki.api 4.0.7
WikiPageLocalService
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageLocalServiceUtil
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageLocalServiceWrapper
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageModel
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPagePersistence
Old: com.liferay.portlet.wiki.service.persistence New:
com.liferay.wiki.service.persistence
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResource
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourceLocalService
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourceLocalServiceUtil
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourceLocalServiceWrapper
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourceModel
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourcePersistence
Old: com.liferay.portlet.wiki.service.persistence New:
com.liferay.wiki.service.persistence
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourceSoap
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourceUtil
Old: com.liferay.portlet.wiki.service.persistence New:
com.liferay.wiki.service.persistence
com.liferay com.liferay.wiki.api 4.0.7
WikiPageResourceWrapper
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageService
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageServiceUtil
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageServiceWrapper
Old: com.liferay.portlet.wiki.service New: com.liferay.wiki.service
com.liferay com.liferay.wiki.api 4.0.7
WikiPageSoap
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
WikiPageUtil
Old: com.liferay.portlet.wiki.service.persistence New:
com.liferay.wiki.service.persistence
com.liferay com.liferay.wiki.api 4.0.7
WikiPageWrapper
Old: com.liferay.portlet.wiki.model New: com.liferay.wiki.model
com.liferay com.liferay.wiki.api 4.0.7
\chapter{Export/Import and Staging}\label{exportimport-and-staging}
Export/Import and Staging are frameworks that help you manage your
content publication. This section provides reference documentation
complementing the
\href{/docs/7-2/frameworks/-/knowledge_base/f/content-publication-management}{Content
Publication Management} section.
\chapter{Decision to Implement
Staging}\label{decision-to-implement-staging}
Staging is an advanced publication tool that lets you create or modify
your site before releasing it to the public. Most of Liferay DXP's
included applications (e.g., Web Content, Bookmarks, etc.) support
Staging. Implementing Staging in your own application can be beneficial,
but how do you know if it's the right move?
Not every application needs to support Staging and Export/Import. The
most important question to consider during the decision process is
\emph{What part of your application are you primarily focused on using
Staging for?}
When Staging is enabled, all pages and applications are staged
automatically. Liferay DXP's architecture separates the application and
its configuration from the actual content, meaning that content can
exist without any application to display it and vice versa. Although
Staging supports all applications and their configurations by default,
not all applications' content is supported by Staging.
Implementing Staging for your application means you're defining the
logic for how the Staging framework should process, serialize, and
de-serialize your app's content, and how to insert it into a database.
Therefore, if you want to track your application's content, you should
implement Staging in your application. Here are a few other scenarios
where you should implement Staging in your application:
\begin{itemize}
\tightlist
\item
You're using remote staging. When publishing to a remote live site,
your content must be transferred to a different Liferay DXP
installation. Therefore, Staging must be able to recognize the content
to facilitate the transfer.
\item
You want a space where you can freely edit and test your content
before publishing it to a live audience.
\item
Your content is being referenced from another content type that
supports Staging.
\item
You want to process your portlet's preferences during publication
(i.e., you might want to publish some content with it or complete
extra steps).
\item
You want to process the content during publication (e.g., writing
validation for your content during the import process).
\end{itemize}
If none of these options are beneficial for you, implementing Staging in
your application is unnecessary.
When content supports Staging and Staging is enabled, it is created in a
Staging group and is only published to a live site when that site is
published. When content is \textbf{not} supported by Staging, it is
never added to a Staging group and is not reviewable during the Staging
publication process; it's added and removed from the live site only.
From a technical standpoint, publishing an entity or content follows the
process below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
The entity's possible references are discovered and processed.
\item
The entity's fields are processed.
\item
The entity is serialized into a LAR file.
\item
The LAR is transferred to the live site (local or remote live).
\item
After de-serialization, the entity's fields are processed.
\item
The entity is added to the database.
\end{enumerate}
Awesome! You should now have a good idea about whether you should
implement Staging for your application.
\chapter{Liferay Archive (LAR) File}\label{liferay-archive-lar-file}
An easier way to export/import your application's data is to use a
Liferay ARchive (LAR) file. Liferay provides the LAR feature to address
the need to export/import data in a database agnostic manner. So what
exactly is a LAR file?
A LAR file is a compressed file (ZIP archive) Liferay DXP uses to
export/import data. LAR files can be created for single portlets, pages,
or sets of pages. Portlets that are LAR-capable provide an interface to
let you control how their data is imported/exported. There are several
Liferay DXP use cases that require the use of LAR files:
\begin{itemize}
\tightlist
\item
Backing up and restoring portlet-specific data without requiring a
full database backup.
\item
Cloning sites.
\item
Specifying a template to be used for users' public or private pages.
\item
Using Local Live or Remote Live staging.
\end{itemize}
The data handler framework is available so developers don't have to
create/modify a LAR file manually. \textbf{It is strongly recommended
never to modify a LAR file.} You should always use Liferay's provided
data handler APIs to construct it.
Knowing how a LAR file is constructed, however, is beneficial to
understand the overall purpose of your application's data handlers.
Next, you'll explore a LAR file's anatomy.
\section{LAR File Anatomy}\label{lar-file-anatomy}
What is a LAR file? You know the general concept for \emph{why} it's
used, but you may want to know what lives inside to make your
export/import processes work. With a fundamental understanding for how a
LAR file is constructed, you can better understand what your data
handlers generate behind the scenes.
Below is the structure of a simple LAR file. It illustrates the
exportation of a single Bookmarks entry and the portlet's configuration:
\begin{itemize}
\tightlist
\item
\texttt{Bookmarks\_Admin-201701091904.portlet.lar}
\begin{itemize}
\tightlist
\item
\texttt{group}
\begin{itemize}
\tightlist
\item
\texttt{20143}
\begin{itemize}
\tightlist
\item
\texttt{com.liferay.bookmarks.model.BookmarksEntry}
\begin{itemize}
\tightlist
\item
\texttt{35005.xml}
\end{itemize}
\item
\texttt{portlet}
\begin{itemize}
\tightlist
\item
\texttt{com\_liferay\_bookmarks\_web\_portlet\_BookmarksAdminPortlet}
\begin{itemize}
\tightlist
\item
\texttt{20137}
\begin{itemize}
\tightlist
\item
\texttt{portlet.xml}
\end{itemize}
\item
\texttt{20143}
\begin{itemize}
\tightlist
\item
\texttt{portlet-data.xml}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{manifest.xml}
\end{itemize}
\end{itemize}
You'll dissect the anatomy structure next.
\section{LAR Manifest}\label{lar-manifest}
You can tell from the LAR's generated name what information is contained
in the LAR: the Bookmarks Admin app's data. The \texttt{manifest.xml}
file sits at the root of the LAR file. It provides essential information
about the export process. The \texttt{manifest.xml} for the sample
Bookmarks LAR is pretty bare since it's not exporting much content, but
this file can become large when exporting pages of content. There are
four main parts (tags) to a \texttt{manifest.xml} file.
\begin{itemize}
\tightlist
\item
\texttt{header}: contains information about the LAR file, current
process, and site you're exporting (if necessary). For example, it can
include locales, build information, export date, company ID, group ID,
layouts, themes, etc.
\item
\texttt{missing-references}: lists entities that must be validated
during import. For example, suppose you're exporting a web content
article that references an image (e.g., an embedded image residing in
the document library). If the image was not selected for export, the
image must already exist in the site where the article is imported.
Therefore, the image would be flagged as a missing reference in the
LAR file. If the missing reference does not exist in the site when the
LAR is imported, the import process fails. If your import fails, the
Import UI shows you the missing references that weren't validated.
\item
\texttt{portlets}: defines the portlets (i.e., portlet data) exported
in the LAR. Each portlet definition has basic information on the
exported portlet and points to the generated \texttt{portlet.xml} for
more specialized portlet information.
\item
\texttt{manifest-summary}: contains information on what has been
exported. The Staging and Export frameworks export or publish some
entities even though they weren't marked for it, because the process
respects data integrity. This section holds information for all the
entities that have been processed. The entities defining a non-zero
\texttt{addition-count} attribute are displayed in the Export/Import
UI.
\end{itemize}
The \texttt{manifest.xml} file also defines layout information if you've
exported pages in your LAR. For example, your manifest could have
\texttt{LayoutSet}, \texttt{Layout}, and \texttt{LayoutFriendlyURL} tags
specifying staged models and their various references in an exported
page.
Now that you've learned about the LAR's \texttt{manifest.xml} and how
it's used to store high-level data about your export process, you can
dive deeper into the LAR file's folders.
\section{LAR Folders}\label{lar-folders}
The \texttt{group} folder has two main parts:
\begin{itemize}
\tightlist
\item
Entities
\item
Portlets
\end{itemize}
If you look at the anatomy of the sample Bookmarks LAR, you'll notice
that \texttt{group/{[}groupId{]}} folder holds a folder named after the
entity you're exporting (e.g.,
\texttt{com.liferay.bookmarks.model.BookmarksEntry}) and a
\texttt{portlet} folder holding a folder named after the portlet from
which you're exporting (e.g.,
\texttt{com\_liferay\_bookmarks\_web\_portlet\_BookmarksAdminPortlet}).
For each entity/portlet you export, there are subsequent folders holding
data about them. Entities and portlets can also be stored in a
\texttt{company} folder. Although the majority of entities belong to a
group, some exist outside of a group scope (e.g., users).
If you open the
\texttt{/group/20143/com.liferay.bookmarks.model.BookmarksEntry/35005.xml}
file, you'll find serialized data about the entity, which is similar to
what is stored in the database.
The \texttt{portlet} folder holds all the portlets you exported. Each
portlet has its own folder that holds various XML files with data
describing the exported content. There are three main XML files that can
be generated for a single portlet:
\begin{itemize}
\tightlist
\item
\texttt{portlet.xml}: provides essential information about the
portlet, similar to a manifest file. For example, this can include the
portlet ID, high-level entity information stored in the portlet (e.g.,
web content articles in a web content portlet), permissioning, etc.
\item
\texttt{portlet-data.xml}: describes specific entity data stored in
the portlet. For example, for the web content portlet, articles stored
in the portlet are defined in \texttt{staged-model} tags and are
linked to their serialized entity XML files.
\item
\texttt{portlet-preferences.xml}: defines the settings of the portlet.
For example, this can include portlet preferences like the portlet
owner, default user, article IDs, etc.
\end{itemize}
Note that when you import a LAR, it only includes the portlet data. You
have to deploy the portlet to be able to use it.
You now know how exported entities, portlets, and pages are defined in a
LAR file. For a summarized outline of what you've learned about LAR file
construction, see the diagram below.
\begin{figure}
\centering
\includegraphics{./images/lar-diagram.png}
\caption{Entities, Portlets, and Pages are defined in a LAR in different
places.}
\end{figure}
Excellent! You now have a fundamental understanding for how a LAR file
is generated and how it's structured.
\chapter{Front-End Reference}\label{front-end-reference}
This section contains resources that you might find useful for Front-End
development.
The topics below are covered in this section:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/product-freemarker-macros}{Liferay
DXP FreeMarker Macros}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/freemarker-taglib-macros}{FreeMarker
Taglib Macros}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs}{Front-end
Taglibs}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-npm-bundler}{Liferay
npm Bundler}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-javascript-apis}{Liferay
JS APIs}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/setting-up-your-npm-environment}{Setting
up Your npm Environment}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/sitemap-page-configuration-options}{Sitemap.json
Page Configuration Options}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/ckeditor-plugin-reference-guide}{CKEditor
Plugin Reference Guide}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/fully-qualified-portlet-ids}{Fully
Qualified Portlet IDs List}
\end{itemize}
\chapter{Liferay DXP FreeMarker
Macros}\label{liferay-dxp-freemarker-macros}
Liferay DXP defines several
\href{https://freemarker.apache.org/docs/ref_directive_macro.html}{macros}
in
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-template/portal-template-freemarker/src/main/resources/FTL_liferay.ftl}{\texttt{FTL\_Liferay.ftl}
template} that you can use in your theme templates to include theme
resources, standard portlets, and more. Liferay DXP also exposes its
taglibs as FreeMarker macros. See each
\href{/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs}{taglib's
documentation} for more information on using the taglib in your
FreeMarker templates. This reference guide lists the available
FreeMarker macros that Liferay DXP offers.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.2500}}
>{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.2500}}
>{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.2500}}
>{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.2500}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Macro
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Parameters
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Example
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
breadcrumbs & default\_preferences & Adds the Breadcrumbs portlet with
optional preferences &
\texttt{\textless{}@liferay.breadcrumbs\ /\textgreater{}} \\
control\_menu & N/A & Adds the Control Menu portlet &
\texttt{\textless{}@liferay.control\_menu\ /\textgreater{}} \\
css & file\_name & Adds an external stylesheet with the specified file
name location &
\texttt{\textless{}@liferay.css\ file\_name="\$\{css\_folder\}/mycss.css"/\textgreater{}} \\
date & format & Prints the date in the current locale with the given
format &
\texttt{\textless{}@liferay.date\ format="/yyyy/MM/dd/HH/"\ /\textgreater{}} \\
js & file\_name & Adds an external JavaScript file with the specified
file name source &
\texttt{\textless{}@liferay.js\ file\_name="\$\{javascript\_folder\}/myJs.js"/\textgreater{}} \\
language & key & Prints the specified language key in the current locale
&
\texttt{\textless{}@liferay.language\ key="last-modified"\ /\textgreater{}} \\
language\_format & argumentskey & Formats the given language key with
the specified arguments. For example, passing \texttt{go-to-x} as the
key and \texttt{Mars} as the arguments prints \emph{Go to Mars}. &
\texttt{\textless{}@liferay.language\_format\ arguments="\$\{site\_name\}"\ key="go-to-x"\ /\textgreater{}} \\
languages & default\_preferences & Adds the Languages portlet with
optional preferences &
\texttt{\textless{}@liferay.languages\ /\textgreater{}} \\
navigation\_menu & default\_preferencesinstance\_id & Adds the
Navigation Menu portlet with optional preferences and instance ID. &
\texttt{\textless{}@liferay.navigation\_menu\ /\textgreater{}} \\
search & default\_preferences & Adds the Search portlet with optional
preferences & \texttt{\textless{}@liferay.search\ /\textgreater{}} \\
search\_bar & default\_preferences & Adds the Search Bar portlet with
optional preferences &
\texttt{\textless{}@liferay.search\_bar\ /\textgreater{}} \\
user\_personal\_bar & N/A & Adds the User Personal Bar portlet &
\texttt{\textless{}@liferay.user\_personal\_bar\ /\textgreater{}} \\
\end{longtable}
\noindent\hrulefill
A few reference examples are shown below.
\section{Reference Examples}\label{reference-examples}
The example below includes a language key with the \texttt{language}
macro directive along with its language \texttt{key} parameter:
\begin{verbatim}
<@liferay.language key="powered-by" />
\end{verbatim}
This example includes the Search portlet with its
\href{/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets\#portlet-decorators}{Portlet
Decorator} portlet preference set to barebone:
\begin{verbatim}
<@liferay.search default_preferences=
freeMarkerPortletPreferences.getPreferences(
"portletSetupPortletDecoratorId", "barebone"
)
/>
\end{verbatim}
You can also pass multiple portlet preferences in an object, as shown in
the example below for the Navigation Menu portlet:
\begin{verbatim}
<#assign secondaryNavigationPreferencesMap =
{
"displayStyle": "ddmTemplate_NAVBAR-BLANK-JUSTIFIED-FTL",
"portletSetupPortletDecoratorId": "barebone",
"rootLayoutType": "relative",
"siteNavigationMenuId": "0",
"siteNavigationMenuType": "1"
}
/>
<@liferay.navigation_menu
default_preferences=
freeMarkerPortletPreferences.getPreferences(secondaryNavigationPreferencesMap)
instance_id="main_navigation_menu"
/>
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} Portlet preferences are unique to each portlet, so first
you must determine which preferences you want to configure. There are
two ways to determine the proper key/value pair for a portlet
preference. The first is to set the portlet preference manually, and
then check the values in the \texttt{portletPreferences.preferences}
column of the database as a hint for what to configure.
Another approach is to search each app in your bundle for the keyword
\texttt{preferences-\/-}. This returns app JSPs that have the portlet
preferences defined for the portlet.
\chapter{Front-End Taglibs}\label{front-end-taglibs}
You have access to a powerful set of taglibs for creating commonly used
UI components in your apps, themes, and web content. The following
taglibs are covered in this section:
\begin{itemize}
\item
AUI: create common UI components such as forms, buttons, and more.
\item
Chart: visualize data. Create bar charts, line charts, scatter charts,
spline charts, and much more.
\item
Clay: create
\href{https://clayui.com/docs/components/alerts.html}{Clay
components}, such as alerts, buttons, drop-down menus, form elements,
and more for your apps.
\item
Frontend: create UI components commonly used throughout Portal's apps,
such as add menus, cards, management bars, and more.
\item
Liferay UI: create common UI components such as icons, tabs, and more.
\item
Liferay Util: load additional resources, define parameters, buffer
content, and more.
\end{itemize}
\noindent\hrulefill
\textbf{Note:} Each taglib is available as a FreeMarker macro, except
for the Chart taglib. The Chart taglib is \textbf{not} available as a
FreeMarker macro. The articles in this section provide the proper syntax
to use for each macro. See the
\href{/docs/7-2/reference/-/knowledge_base/r/product-freemarker-macros}{FreeMarker
Taglib Mappings reference} for a complete list of the available
FreeMarker taglib macros.
\noindent\hrulefill
In this section, you'll learn how to use taglibs to build awesome user
interfaces for your apps!
\chapter{Liferay Theme Objects Available in
JSPs}\label{liferay-theme-objects-available-in-jsps}
When you include the
\texttt{\textless{}liferay-theme:defineObjects\textgreater{}} tag in
your JSP, you gain access to several Liferay theme objects via
variables. These objects are described in the table below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Object
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{account} & The user's Account object. This object maps to the
Account table in the Liferay database. \\
\texttt{colorScheme} & An object representing the current color scheme
in the theme that is being rendered by the portal \\
\texttt{company} & The current Company object. This represents the
portal instance on which the user is currently navigating. \\
\texttt{contact} & The user's Contact object. This object maps to the
Contact table in the Liferay database. \\
\texttt{layout} & The page to which the user has currently navigated \\
\texttt{layoutTypePortlet} & This object can be used to programmatically
add or remove portlets from a page. \\
\texttt{locale} & The current user's locale, as defined by Java \\
\texttt{permissionChecker} & An object that can determine---given a
particular resource---whether the current user has a particular
permission for that resource \\
\texttt{plid} & A portal layout ID. This is a unique identifier for any
page that exists in the portal, across all portal instances. \\
\texttt{portletDisplay} & An object that gives the programmer access to
many attributes of the current portlet, including the portlet name, the
portlet mode, the ID of the column on the layout in which it resides,
and more \\
\texttt{realUser} & When an administrator is impersonating a user, this
variable tracks the administrator's User object. \\
\texttt{scopeGroupId} & By default, contains the groupId for the
community or organization in which this portlet resides. If the
scopeable attribute is set to true, this may contain a unique scope
identifier for custom scopes, such as the page scope, if the portlet has
been configured to use a custom scope. \\
\texttt{theme} & An object representing the current theme that is being
rendered by the portal \\
\texttt{themeDisplay} & A runtime object that contains many useful
items, such as the logged-in user, the layout, logo information, paths,
and much more \\
\texttt{timeZone} & The current user's time zone, as defined by Java \\
\texttt{user} & The User object representing the current user \\
\end{longtable}
\chapter{Liferay Portlet Objects Available in
JSPs}\label{liferay-portlet-objects-available-in-jsps}
You may have noticed the
\texttt{\textless{}liferay-portlet:defineObjects\textgreater{}} tag in
your JSPs. Similar to the
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-theme-objects-available-in-jsps}{theme:defineObjects}
tag, when you include this tag in your JSP, you gain access to several
variables that, in this case, return useful information about your
portlet. Note that the JSR-286 specification defines four lifecycle
methods for a portlet: processAction, processEvent, render, and
serveResource. Some of the variables defined by the
\texttt{\textless{}portlet:defineObjects/\textgreater{}} tag are only
available to a JSP if the JSP was included during the appropriate phase
of the portlet lifecycle. These objects are described in the table
below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.3529}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.6471}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Object
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{ActionRequest\ actionRequest} & Represents the request sent to
the portlet to handle an action. \texttt{actionRequest} is only
available to a JSP if the JSP was included during the action-processing
phase. \\
\texttt{ActionResponse\ actionResponse} & Represents the portlet
response to an action request. \texttt{actionResponse} is only available
to a JSP if the JSP was included in the action-processing phase. \\
\texttt{EventRequest\ eventRequest} & Represents the request sent to the
portlet to handle an event. \texttt{eventRequest} is only available to a
JSP if the JSP was included during the event-processing phase. \\
\texttt{EventResponse\ eventResponse} & Represents the portlet response
to an event request. \texttt{eventResponse} is only available to a JSP
if the JSP was included in the event-processing phase. \\
\texttt{HeaderRequest\ headerRequest} & Represents the request sent to
the portlet to handle its HTML header or HEAD section.
\texttt{headerRequest} is only available to a JSP if the JSP was
included during the header-processing phase. \\
\texttt{HeaderResponse\ headerResponse} & Represents the portlet
response to a header request. \texttt{headerResponse} is only available
to a JSP if the JSP was included in the header-processing phase. \\
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/LiferayPortletRequest.html}{\texttt{LiferayPortletRequest\ liferayPortletRequest}}
& Provides access to the \texttt{HttpServletRequest}, the
\texttt{Portlet}, and the portlet name and lifecycle value.
\texttt{liferayPortletRequest} is available in all portlet phases. \\
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/LiferayPortletResponse.html}{\texttt{LiferayPortletResponse\ liferayPortletResponse}}
& Includes the properties returned to the portal and provides a means to
add or change properties. \texttt{liferayPortletResponse} is available
in all portlet phases. \\
\texttt{RenderRequest\ renderRequest} & Represents the request sent to
the portlet to render the portlet. \texttt{renderRequest} is only
available to a JSP if the JSP was included during the render request
phase. \\
\texttt{RenderResponse\ renderResponse} & Represents an object that
assists the portlet in sending a response to the portal.
\texttt{renderResponse} is only available to a JSP if the JSP was
included during the render request phase. \\
\texttt{ResourceRequest\ resourceRequest} & Represents the request sent
to the portlet for rendering resources. \texttt{resourceRequest} is only
available to a JSP if the JSP was included during the resource-serving
phase. \\
\texttt{ResourceResponse\ resourceResponse} & Represents an object that
assists the portlet in rendering a resource. \texttt{resourceResponse}
is only available to a JSP if the JSP was included in the
resource-serving phase. \\
\texttt{PortletConfig\ portletConfig} & Represents the portlet's
configuration including, the portlet's name, initialization parameters,
resource bundle, and application context. \texttt{portletConfig} is
always available to a portlet JSP, regardless of the request-processing
phase in which it was included. \\
\texttt{PortletPreferences\ portletPreferences} & Provides access to a
portlet's preferences. \texttt{portletPreferences} is always available
to a portlet JSP, regardless of the request-processing phase in which it
was included. \\
\texttt{Map\textless{}String,\ String{[}{]}\textgreater{}\ portletPreferencesValues}
& Provides a Map equivalent to the \texttt{portletPreferences.getMap()}
call or an empty Map if no portlet preferences exist. \\
\texttt{PortletSession\ portletSession} & Provides a way to identify a
user across more than one request and to store transient information
about a user. A \texttt{portletSession} is created for each user client.
\texttt{portletSession} is always available to a portlet JSP, regardless
of the request-processing phase in which it was included.
\texttt{portletSession} is \texttt{null} if no session exists. \\
\texttt{Map\textless{}String,\ Object\textgreater{}\ portletSessionScope}
& Provides a Map equivalent to the
\texttt{PortletSession.getAtrributeMap()} call or an empty Map if no
session attributes exist. \\
\end{longtable}
\noindent\hrulefill
For more details, visit the
\href{https://docs.liferay.com/portlet-api/3.0/javadocs/}{Portlet 3.0
API Javadoc}.
\chapter{Using the Liferay UI Taglib}\label{using-the-liferay-ui-taglib}
The Liferay UI tag library provides tags that implement commonly used UI
components. These tags make your markup consistent, responsive, and
accessible.
You can find a list of the available Liferay UI taglibs in the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/tld-summary.html}{Liferay
UI taglibdocs}. Each taglib has a list of attributes that can be passed
to the tag. Some of these are required and some are optional. See the
taglibdocs to view the requirements for each tag. You'll find the full
markup generated by the tags in their JSPs in their
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/portal-web/docroot/html/taglib/ui}{Liferay
Github Repo} folders.
To use the Liferay-UI taglib library in your apps, you must add the
following declaration to your JSP:
\begin{verbatim}
<%@ taglib prefix="liferay-ui" uri="http://liferay.com/tld/ui" %>
\end{verbatim}
The Liferay-UI taglib is also available via a macro for your FreeMarker
theme and web content templates. Follow this syntax:
\begin{verbatim}
<@liferay_ui["tag-name"] attribute="string value" attribute=10 />
\end{verbatim}
This section covers how to create UI components with the Liferay UI
taglibs. Each article contains code examples along with a screenshot of
the resulting UI.
\chapter{Liferay UI Icons}\label{liferay-ui-icons}
The Liferay UI taglibs provide several icons you can include in your
apps. To add an icon to your app, use the \texttt{liferay-ui:icon} tag
and specify the icon with either the \texttt{icon},
\texttt{iconCssClass}, or \texttt{image} attribute. An example of each
use case is shown below.
The \texttt{image} attribute specifies
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-theme/frontend-theme-unstyled/src/main/resources/META-INF/resources/_unstyled/images}{Liferay
UI icons} to use (as defined in the Unstyled theme's
\texttt{images/common} folder). Here's an example configuration for a
JSP:
\begin{verbatim}
Subscribe
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-icon-subscribe.png}
\caption{Use the image attribute to use a theme icon.}
\end{figure}
The Liferay UI taglib also exposes language flag icons. To use a
language flag icon, provide the \texttt{../language/} relative path
before the icon's name. Below is an example snippet from the Web Content
Search portlet that displays the current language's flag along with a
localized message:
\begin{verbatim}
\end{verbatim}
You can achieve the same result in FreeMarker with the following code
that uses the available
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-theme/frontend-theme-unstyled/src/main/resources/META-INF/resources/_unstyled/templates/init.ftl}{\texttt{init.ftl}
variables} and
\href{/docs/7-2/reference/-/knowledge_base/r/product-freemarker-macros}{Liferay
DXP macros}:
\begin{verbatim}
<#assign flag_message>
<@liferay.language_format
arguments=language
key="this-result-comes-from-the-x-version-of-this-content"
/>
#assign>
<@liferay_ui["icon"]
image="../language/${language_id}"
message=flag_message
/>
\end{verbatim}
The full list of available icons is shown in the figures below:
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-icons.png}
\caption{The Liferay UI taglib offers multiple icons for use in your
app.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-icon-flags.png}
\caption{Liferay UI icons can be configured based on language.}
\end{figure}
The \texttt{icon} attribute specifies
\href{https://fontawesome.com/v3.2.1/icons/}{Font Awesome icons} to use:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-icon-angle-down.png}
\caption{You can use the icon attribute to include Font Awesome icons in
your app.}
\end{figure}
The \texttt{iconCssClass} attribute specifies a
\href{http://marcoceppi.github.io/bootstrap-glyphicons/}{glyphicon} to
use:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-icon-css-class.png}
\caption{You can use Font Awesome icons in your app.}
\end{figure}
The examples above use some of the icon's available attributes. See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/icon.html}{Icon
taglibdocs} for the full list.
\section{Related Topics}\label{related-topics}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icon-lists}{Liferay
UI Icon Lists}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icon-menus}{Liferay
UI Icon Menus}
\end{itemize}
\chapter{Liferay UI Icon Lists}\label{liferay-ui-icon-lists}
An icon list displays icons in a horizontal list, instead of in a pop-up
navigation menu like an
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icon-menus}{icon
menu}. You can see an example of an icon list menu in a message board
thread. The thread's actions are visible at all times for
administrators:
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-icon-list.png}
\caption{Icon lists display an app's actions at all times.}
\end{figure}
Create the list menu with the \texttt{liferay-ui:icon-list} tag and nest
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icons}{icons}
for each list item, as shown below:
\begin{verbatim}
\end{verbatim}
See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/icon-list.html}{Icon
List taglibdocs} for the full list of available attributes.
\section{Related Topics}\label{related-topics-1}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icon-menus}{Liferay
UI Icon Menus}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icons}{Liferay
UI Icons}
\end{itemize}
\chapter{Liferay UI Icon Menus}\label{liferay-ui-icon-menus}
You can add a pop-up navigation menu to your app with the
\texttt{liferay-ui:icon-menu} tag. Icon menus display menu options when
needed, storing them away in a collapsed menu when they're not. This
keeps the UI clean and uncluttered. Just as with an icon list, you nest
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icons}{icons}
for each navigation item. You can see an example of a icon menu in a
site's actions menu in the My Sites portlet:
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-icon-menu.png}
\caption{Setting up an icon menu is a piece of cake.}
\end{figure}
Example JSP configuration:
\begin{verbatim}
\end{verbatim}
Note that the \texttt{url} attribute is required for icons to render
properly. See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/icon-menu.html}{Icon
Menu taglibdocs} for the full list of attributes.
\section{Related Topics}\label{related-topics-2}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icon-lists}{Liferay
UI Icon Lists}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icons}{Liferay
UI Icons}
\end{itemize}
\chapter{Liferay UI Tabs}\label{liferay-ui-tabs}
Tabs create dividers that organize content into individual sections.
Content can be embedded or included from another JSP.
To add tabs to your app, use the
\texttt{\textless{}liferay-ui:tabs\textgreater{}} tag and specify each
tab's name as a comma-separated list for the \texttt{names} attribute.
For example, three tabs named \texttt{tab1}, \texttt{tab2}, and
\texttt{tab3}, look like this in the JSP:
\begin{verbatim}
\end{verbatim}
Each tab requires a corresponding section to display content. Nest
\texttt{liferay-ui:section} tags for each of the tabs. Within each
section, you can add HTML content or add content indirectly by including
content from another JSP (via the
\texttt{\textless{}\%@\ includefile="filepath"\%\textgreater{}}
directive). The example snippet below is from the Calendar portlet's
\texttt{configuration.jsp}:
\begin{verbatim}
<%@ include file="/configuration/user_settings.jspf" %>
<%@ include file="/configuration/display_settings.jspf" %>
<%@ include file="/configuration/rss.jspf" %>
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-tabs.png}
\caption{Tabs are a useful way to organize configuration options into
individual sections within the same UI.}
\end{figure}
The example above uses some of the tab's available attributes. See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/tabs.html}{Tabs
taglibdocs} for the full list of attributes.
\section{Related Topics}\label{related-topics-3}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-navigation-bars}{Clay
Navigation Bars}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-dropdown-menus-and-action-menus}{Clay
Dropdown Menus and Action Menus}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icon-help}{Liferay
UI Icon Help}
\end{itemize}
\chapter{Liferay UI Icon Help}\label{liferay-ui-icon-help}
The icon help tag lets you communicate additional information to your
users in an unobtrusive way. It renders as an iconic question mark that
provides more information through a pop-up tooltip on mouse over. You
can see an example of this in the Control Panel:
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-tooltip.png}
\caption{Here's an example of the icon help tag.}
\end{figure}
\noindent\hrulefill
\textbf{Note:} If you have installed a custom theme you may also need to
add the following imports to your \texttt{view.jsp} to make
\texttt{liferay-ui:icon-help} tag work:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>
\end{verbatim}
\noindent\hrulefill
Add the \texttt{\textless{}liferay-ui:icon-help/\textgreater{}} tag next
to the UI that needs tooltip information. Define the informational text
with the required \texttt{message} attribute. Below is an example
snippet for one of the Server Administration's clean up actions:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-ui-taglib-tooltip-02.png}
\caption{help icons are used throughout the Control Panel.}
\end{figure}
Note that the message is supplied via a
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application}{language
key}. While you can use a string for the tooltip's message for testing
purposes, a language key is considered best practice and should be used
in production.
\section{Related Topics}\label{related-topics-4}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-badges}{Clay Badges}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-stickers}{Clay
Stickers}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-ui-icon-menus}{Liferay
UI Icon Menus}
\end{itemize}
\chapter{Using Liferay Front-end Taglibs in Your
Portlet}\label{using-liferay-front-end-taglibs-in-your-portlet}
The Liferay Front-end tag library provides a set of tags for creating
common front-end UI components in your app.
To use the Front-end taglib in you apps, add the following declaration
to your JSP:
\begin{verbatim}
<%@ taglib prefix="liferay-frontend" uri="http://liferay.com/tld/frontend" %>
\end{verbatim}
The Liferay Front-end taglib is also available via a macro for your
FreeMarker theme templates and web content templates. Follow this
syntax:
\begin{verbatim}
<@liferay_frontend["tag-name"] attribute="string value" attribute=10 />
\end{verbatim}
The following Front-end UI components are covered in this section:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-add-menu}{Add
Menu}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-cards}{Cards}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-info-bar}{Info
Bar}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-management-bar}{Management
Bar}
\end{itemize}
Each article contains a set of examples along with a screenshot of the
resulting UI.
\chapter{Liferay Front-end Add Menu}\label{liferay-front-end-add-menu}
The add menu tag creates an add menu button for one or multiple items.
It's used for actions that add entities (e.g.~a new blog entry), and is
part of the Management Bar. Use the
\texttt{\textless{}liferay-frontend:add-menu\textgreater{}} tag to
create the add menu and nest a
\texttt{\textless{}liferay-frontend:add-menu-item\textgreater{}} tag for
each item.
\noindent\hrulefill
\textbf{Note:} This pattern is deprecated as of 7.0. We recommend that
you use the Clay Management Toolbar's
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar\#creation-menu}{creation
menu pattern} instead.
\noindent\hrulefill
When the menu has one item, the button triggers the item's action as
shown in the example below for the Blogs Admin App:
\begin{verbatim}
...
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-add-menu-one-item.png}
\caption{The add button pattern consists of an \texttt{add-menu} tag and
at least one \texttt{add-menu-item} tag.}
\end{figure}
When the menu has multiple items, they display in a pop-up menu. For
example, the Message Boards Admin application has the configuration
below:
\begin{verbatim}
...
...
...
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-add-menu-items.png}
\caption{The add button pattern consists of an \texttt{add-menu} tag and
at least one \texttt{add-menu-item} tag.}
\end{figure}
The examples above use some of the available attributes. See the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/add-menu.html}{add
menu} and
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/add-menu-item.html}{add
menu item} taglibdocs for the full list of available attributes for the
tags.
\section{Related Topics}\label{related-topics-5}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-cards}{Liferay
Frontend Cards}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-info-bar}{Liferay
Frontend Info Bar}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-management-bar}{Liferay
Frontend Management Bar}
\end{itemize}
\chapter{Liferay Front-end Cards}\label{liferay-front-end-cards}
If you have data you want to compare that's heavy on image usage, cards
are the component for the job. Cards visually represent data in a
minimal and compact format. Use them for images, document libraries,
user profiles, and more. There are four main types of Cards:
\begin{itemize}
\tightlist
\item
Horizontal Cards
\item
Icon Cards
\item
Vertical Cards
\item
User Cards
\end{itemize}
Examples of each card are shown below.
\section{Horizontal Card}\label{horizontal-card}
Horizontal cards are used primarily to display documents, such as files
and folders. An example configuration is shown below:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-cards-horizontal.png}
\caption{Horizontal cards are perfect to display files and documents.}
\end{figure}
The
\texttt{\textless{}liferay-frontend:horizontal-card-icon\textgreater{}}
tag uses \href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay
Icons} for its \texttt{icon} attribute.
\section{Icon Vertical Card}\label{icon-vertical-card}
Icon vertical cards, as the name suggests, are cards that display
information in a vertical format that emphasizes an icon. These cards
show content that doesn't have an associated image. Instead, an icon
representing the type of content is displayed. The example snippet below
displays information for a web content article:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-cards-icon-vertical.png}
\caption{Vertical icon cards are perfect to display an entity selection,
such as a web content article.}
\end{figure}
\section{Vertical Card}\label{vertical-card}
Vertical cards display information in a vertical card format, as opposed
to a horizontal format. If the content has an associated image (like a
blog header image) you can use a vertical card to display the image. If
there is no associated image, you can use an icon vertical card to
represent the content's type instead (e.g.~a PDF file). The example
below displays a vertical card for a web content article when an image
preview is available:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-cards-vertical.png}
\caption{Vertical cards are perfect to display files and documents.}
\end{figure}
\section{HTML Vertical Card}\label{html-vertical-card}
The HTML Vertical card lets you display custom HTML in the header of the
vertical card. The example below embeds a video:
\begin{verbatim}
VIDEO
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-cards-html-vertical.png}
\caption{Html vertical cards let you display custom HTML in the card's
header.}
\end{figure}
\section{User Vertical Card}\label{user-vertical-card}
The User Vertical card displays user profile selections in the icon view
of the Management Bar. Below is an example snippet from the User Admin
portlet:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-cards-user-vertical.png}
\caption{User vertical cards are perfect to display files and
documents.}
\end{figure}
\section{Related Topics}\label{related-topics-6}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-add-menu}{Liferay
Front-end Add Menu}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-info-bar}{Liferay
Front-end Info Bar}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-management-bar}{Liferay
Front-end Management Bar}
\end{itemize}
\chapter{Liferay Front-end Info Bar}\label{liferay-front-end-info-bar}
An info bar provides a button that toggles the visibility of additional
sidebar information. This is perfect for providing more detailed
metadata for a search result, such as the file size, type, URL, etc.
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-info-bar-article.png}
\caption{The info bar tags create a sidebar panel toggler that reveals
additional info.}
\end{figure}
The configuration has two key parts: the info bar---and buttons---and
the sidebar panel.
Info bar:
\begin{verbatim}
\end{verbatim}
The
\texttt{\textless{}liferay-frontend:info-bar-sidenav-toggler-button\textgreater{}}
tag uses \href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay
Icons} for the \texttt{icon} attribute.
Sidebar panel:
\begin{verbatim}
sidebar content
Here is some content
\end{verbatim}
Note that the sidebar panel's wrapper
\texttt{\textless{}div\textgreater{}} has the classes \texttt{closed}
and \texttt{sidenav-right}. The info button toggles the classes
\texttt{open} and \texttt{closed}, showing and hiding the sidebar panel.
The \texttt{sidenav-right} class specifies that the panel should open on
the right.
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-info-bar.png}
\caption{The info bar tags create a sidebar panel toggler that reveals
additional info.}
\end{figure}
The examples above use some of the available attributes. See the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/info-bar.html}{info
bar},
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/info-bar-buttons.html}{info
bar buttons},
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/info-bar-sidenav-toggler-button.html}{info
bar sidenav toggler button}, and
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/sidebar-panel.html}{sidebar
panel} taglibdocs for the full list of available attributes for the
tags.
\section{Related Topics}\label{related-topics-7}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-add-menu}{Liferay
Front-end Add Menu}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-cards}{Liferay
Front-end Cards}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-front-end-management-bar}{Liferay
Front-end Management Bar}
\end{itemize}
\chapter{Liferay Front-end Management
Bar}\label{liferay-front-end-management-bar}
The Management Bar gives administrators control over search container
results. It lets you filter, sort, and choose a display style for search
results, so you can quickly identify the document, web content, asset
entry, or whatever you're looking for in your app. The Management Bar is
fully customizable, so you can implement all the controls, or just the
ones your app requires.
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-message-boards.png}
\caption{The Management Bar lets the user customize how the app displays
content.}
\end{figure}
\noindent\hrulefill
\textbf{Note:} The Liferay Front-end Management Bar is deprecated as of
7.0. We recommend that you use the
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Clay
Management Toolbar} instead.
\noindent\hrulefill
The Management Bar has a few key sections. Each section is grouped and
configured using different taglibs:
The
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/management-bar-buttons.html}{\texttt{\textless{}liferay-frontend:management-bar-buttons\textgreater{}}
tag} wraps the Management Bar's button elements:
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-buttons.png}
\caption{The \texttt{management-bar-buttons} tag contains the Management
Bar's main buttons.}
\end{figure}
The
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/management-bar-sidenav-toggler-button.html}{\texttt{\textless{}liferay-frontend:management-bar-sidenav-toggler-button\textgreater{}}
tag} implements slide-out navigation for the info button.
The
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/management-bar-display-buttons.html}{\texttt{\textless{}liferay-frontend:management-bar-display-buttons\textgreater{}}
tag} renders the app's display style options.
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-display-buttons.png}
\caption{The \texttt{management-bar-display-buttons} tag contains the
content's display options.}
\end{figure}
The
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/management-bar-filters.html}{\texttt{\textless{}liferay-frontend:management-bar-filters\textgreater{}}
tag} wraps the app's filtering options. This filter should be included
in all control panel applications. Filtering options can include sort
criteria, sort ordering, and more.
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-filters.png}
\caption{The \texttt{management-bar-filters} tag contains the content
filtering options.}
\end{figure}
Finally, the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-frontend/management-bar-action-buttons.html}{\texttt{\textless{}liferay-frontend:management-bar-action-buttons\textgreater{}}
tag} wraps the actions that you can execute over selected items. You can
select multiple items between pages. The management bar keeps track of
the number of selected items for you.
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-action-buttons.png}
\caption{The management bar keeps track of the items selected and
displays the actions to execute on them.}
\end{figure}
For example, here's the Management Bar configuration in the Trash app:
\begin{verbatim}
\end{verbatim}
\chapter{Including Actions in the Management
Bar}\label{including-actions-in-the-management-bar}
While an actions menu is typically included with each search container
result, you can also include these actions in the management bar. This
keeps everything organized within the same UI. This update adds a
checkbox next to each search container result, as well as adds one in
the management bar itself to select all results. The actions are
displayed when a checkbox is checked---individual or select all---and
hidden from view otherwise.
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-include-checkbox.png}
\caption{You can select individual results or all results at once.}
\end{figure}
Follow these steps to include actions in your management bar:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Update the
\texttt{\textless{}liferay-frontend:management-bar\textgreater{}} tag
to include the checkbox and provide the search container's ID:
\begin{verbatim}
\end{verbatim}
\item
After the closing
\texttt{\textless{}/liferay-frontend:management-bar-filters\textgreater{}}
tag, add the
\texttt{\textless{}liferay-frontend:management-bar-action-buttons\textgreater{}}
tags:
\begin{verbatim}
\end{verbatim}
\item
Use the available management bar button taglibs
(e.g.~\texttt{management-bar-button}) to build the action buttons for
your app's management bar. A code snippet from the Site admin portlet
is shown below:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-actions.png}
\caption{You can have as many actions as your app requires.}
\end{figure}
\section{Related Topics}\label{related-topics-8}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/disabling-all-or-portions-of-the-management-bar}{Disabling
All or Portions of the Management Bar}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Clay
Management Toolbar}
\end{itemize}
\chapter{Disabling All or Portions of the Management
Bar}\label{disabling-all-or-portions-of-the-management-bar}
When there are no search results to display, you should disable all the
Management Bar's buttons, except the sidenav toggler button.
You can disable the Management Bar by adding the \texttt{disabled}
attribute to the \texttt{liferay-frontend:management-bar} tag:
\begin{verbatim}
\end{verbatim}
You can also disable individual components by adding the
\texttt{disabled} attribute to the corresponding tag. The example below
disables the display buttons when the search container displays 0
results, since changing the display style has no effect when there
aren't any results to view:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-frontend-taglib-management-bar-disabled.png}
\caption{You can disable all or portions of the Management Bar.}
\end{figure}
\section{Related Topics}\label{related-topics-9}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/including-actions-in-the-management-bar}{Including
Actions in the Management Bar}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Clay
Management Toolbar}
\end{itemize}
\chapter{Using the Liferay Util
Taglib}\label{using-the-liferay-util-taglib}
The Liferay Util taglib is used to pull other resources into a portlet
or theme. You can use it to specify which resources to insert at the
bottom or top of the page's HTML.
To use the Liferay-Util taglib, add the following declaration to your
JSP:
\begin{verbatim}
<%@ taglib prefix="liferay-util" uri="http://liferay.com/tld/util" %>
\end{verbatim}
The Liferay-Util taglib is also available via a macro for your
FreeMarker theme templates and web content templates. Follow this
syntax:
\begin{verbatim}
<@liferay_util["tag-name"] attribute="string value" attribute=10 />
\end{verbatim}
This section covers the available Liferay Util tags you can use in your
app to inject content into portlets and themes.
\chapter{Using Liferay Util Body
Bottom}\label{using-liferay-util-body-bottom}
The body bottom tag is not a self-closing tag. It lets you add
additional HTML or scripts to the bottom of the \texttt{body} tag.
content placed between the opening and closing of this tag is passed to
the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-web/docroot/html/common/themes/body_bottom.jsp\#L26-L31}{body\_bottom.jsp}
and outputs in this JSP.
This tag also has an optional \texttt{outputKey} attribute. If several
portlets on the page include the same resource with this tag, you can
specify the same \texttt{outputKey} value for each tag so the resource
is only loaded once.
The example configuration below uses the
\texttt{\textless{}liferay-util:body-bottom\textgreater{}} tag to
include JavaScript provided by the portlet's bundle:
\begin{verbatim}
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:body-bottom\textgreater{}} tag to
include additional resources in the bottom of the page's body.
\section{Related Topics}\label{related-topics-10}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-body-top}{Using
the Liferay Util HTML Body Top Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-html-top}{Using
the Liferay Util HTML Top Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-liferay-ui-taglib}{Using
the Liferay UI Taglib}
\end{itemize}
\chapter{Using Liferay Util Body Top}\label{using-liferay-util-body-top}
The body top tag is not a self-closing tag. The content placed between
the opening and closing of this tag is moved to the top of the
\texttt{body} tag. When something is passed using this taglib, the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-web/docroot/html/common/themes/body_top.jsp\#L25-L31}{body\_top.jsp}
is passed markup and outputs in this JSP.
This tag also has an optional \texttt{outputKey} attribute. If several
portlets on the page include the same resource with this tag, you can
specify the same \texttt{outputKey} value for each tag so the resource
is only loaded once.
The example configuration below uses the
\texttt{\textless{}liferay-util:body-top\textgreater{}} tag to include
JavaScript provided by the portlet's bundle:
\begin{verbatim}
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:body-top\textgreater{}} tag to include
additional resources in the top of the page's body.
\section{Related Topics}\label{related-topics-11}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-body-bottom}{Using
the Liferay Util HTML Body Bottom Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-html-bottom}{Using
the Liferay Util HTML Bottom Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-clay-taglib-in-your-portlets}{Using
the Clay Taglib}
\end{itemize}
\chapter{Using Liferay Util Buffer}\label{using-liferay-util-buffer}
The buffer tag is not a self-closing tag. The content placed between the
opening and closing of this tag is saved to a buffer and its output is
assigned to the Java variable declared with the tag's \texttt{var}
attribute. The output is returned as a String, letting you post-process
it. For example, you can use this to
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-osgi-fragments\#provide-the-overridden-jsp}{override
a JSP's existing contents}.
The example below saves the link's generated markup to a buffer and then
uses the returned string as the argument for a
\texttt{liferay-ui:message} key:
\begin{verbatim}
Liferay
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:buffer\textgreater{}} tag to save
content to a buffer.
\begin{figure}
\centering
\includegraphics{./images/liferay-util-buffer.png}
\caption{You can use the Liferay Util Buffer tag to save pieces of
markup to reuse in your JSP.}
\end{figure}
\section{Related Topics}\label{related-topics-12}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-osgi-fragments\#provide-the-overridden-jsp}{JSP
Overrides Using OSGi Fragments}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-param}{Using
the Liferay Util Param Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-front-end-taglibs-in-your-portlet}{Using
the Liferay Front-End Taglibs}
\end{itemize}
\chapter{Using Liferay Util Dynamic
Include}\label{using-liferay-util-dynamic-include}
The dynamic include tag lets you specify a point or points in a JSP or
theme where a developer can inject additional HTML, resources, or
functionality, using the \texttt{DynamicIncludeRegistry}. You can read
more about the OSGi Service Registry
\href{http://docs.spring.io/osgi/docs/current/reference/html/service-registry.html}{here}.
The \texttt{key} attribute identifies the extension point. See
\href{/docs/7-2/customization/-/knowledge_base/c/dynamic-includes}{Dynamic
Includes} for example configurations that use dynamic include extension
points to inject additional functionality.
The example configuration below uses the
\texttt{\textless{}liferay-util:dynamic-include\textgreater{}} tag to
include an extension point before the primary code and an extension
point after the primary code:
\begin{verbatim}
<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util" %>
And here we have our content
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:dynamic-include\textgreater{}} tag to
add extension points to your app.
\section{Related Topics}\label{related-topics-13}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/dynamic-includes}{Dynamic
Includes}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-body-top}{Using
the Liferay Util Body Top Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-chart-taglib-in-your-portlets}{Using
the Chart Taglib}
\end{itemize}
\chapter{Using Liferay Util Get URL}\label{using-liferay-util-get-url}
The get URL tag scrapes the URL provided by the \texttt{url} attribute.
If a value is provided for the \texttt{var} attribute, the content from
the screen scrape is scoped to that variable. Otherwise, the scraped
content is displayed where the taglib is used.
A basic configuration for the
\texttt{\textless{}liferay-util:get-url\textgreater{}} tag is shown
below:
\begin{verbatim}
\end{verbatim}
Here is an example that uses the \texttt{var} attribute:
\begin{verbatim}
We borrowed Liferay . Here it is.
<%= Liferay %>
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-util-get-url-ldn.png}
\caption{You can use the Liferay Util Get URL tag to scrape URLs.}
\end{figure}
Now you know how to use the
\texttt{\textless{}liferay-util:get-url\textgreater{}} tag to scrape
URLs.
\section{Related Topics}\label{related-topics-14}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-param}{Using
the Liferay Util Param Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-include}{Using
the Liferay Util Include Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-aui-taglibs}{Using
the AUI Taglib}
\end{itemize}
\chapter{Using Liferay Util HTML
Bottom}\label{using-liferay-util-html-bottom}
The HTML bottom tag is not a self-closing tag. Content placed between
the opening and closing of this tag is moved to the bottom of the
\texttt{\textless{}html\textgreater{}} tag. When something is passed
using this taglib, the
\href{https://github.com/liferay/liferay-portal/blob/master/portal-web/docroot/html/common/themes/bottom.jsp\#L53-L59}{bottom.jsp}
is passed markup and outputs in this JSP.
This tag also has an optional \texttt{outputKey} attribute. If several
portlets on the page include the same resource with this tag, you can
specify the same \texttt{outputKey} value for each tag so the resource
is only loaded once.
The example configuration below uses the
\texttt{\textless{}liferay-util:html-bottom\textgreater{}} tag to
include JavaScript (a common use case) provided by the portlet's bundle:
\begin{verbatim}
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:html-bottom\textgreater{}} tag to
include additional resources in the bottom of the page's HTML tag.
\section{Related Topics}\label{related-topics-15}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-body-bottom}{Using
the Liferay Util HTML Body Bottom Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-html-top}{Using
the Liferay Util HTML Top Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-liferay-ui-taglib}{Using
the Liferay UI Taglib}
\end{itemize}
\chapter{Using Liferay Util HTML Top}\label{using-liferay-util-html-top}
The HTML top tag is not a self-closing tag. The content placed between
the opening and closing of this tag is moved to the
\texttt{\textless{}head\textgreater{}} tag. When something is passed
using this taglib, the
\href{https://github.com/liferay/liferay-portal/blob/master/portal-web/docroot/html/common/themes/top_head.jsp\#L147-L153}{top\_head.jsp}
is passed markup and outputs in this JSP.
This tag also has an optional \texttt{outputKey} attribute. If several
portlets on the page include the same resource with this tag, you can
specify the same \texttt{outputKey} value for each tag so the resource
is only loaded once.
The example configuration below uses the
\texttt{\textless{}liferay-util:html-top\textgreater{}} tag to include
additional CSS styles provided by the portlet's bundle:
\begin{verbatim}
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:html-top\textgreater{}} tag to include
additional resources in the top of the page's HTML tag.
\section{Related Topics}\label{related-topics-16}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-html-bottom}{Using
the Liferay Util HTML Bottom Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-body-top}{Using
the Liferay Util Body Top Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-clay-taglib-in-your-portlets}{Using
the Clay Taglib}
\end{itemize}
\chapter{Using Liferay Util Include}\label{using-liferay-util-include}
The include tag lets you include other JSP files in your portlet's JSP,
theme, or web content. This can increase readability as well as provide
separation of concerns for JSP files.
The \texttt{page} attribute is required and specifies the path to the
JSP or JSPF to include. The \texttt{servletContext} refers to the
request context that the included JSP should use. Passing
\texttt{\textless{}\%=\ application\ \%\textgreater{}} to this attribute
lets the included JSP use the same \texttt{request} object as other
objects that might be set in the prior JSP.
Below is an example configuration for the
\texttt{\textless{}liferay-util:include\textgreater{}} tag:
\begin{verbatim}
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:include\textgreater{}} tag to include
other JSPs in your portlets, themes, and web content.
\section{Related Topics}\label{related-topics-17}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-param}{Using
the Liferay Util Param Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-dynamic-include}{Using
the Liferay Util Dynamic Include Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-front-end-taglibs-in-your-portlet}{Using
the Liferay Front-End Taglibs}
\end{itemize}
\chapter{Using Liferay Util Param}\label{using-liferay-util-param}
The param tag lets you set a parameter for an
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-include}{included
JSP page}. This configuration requires two JSPs. JSP A, the main view of
the app, includes JSP B and sets its parameter value. This lets you
dynamically set content when you include the JSP.
For example, say you have your main functionality in
\texttt{my-app.jsp}, and you have additional functionality provided by
\texttt{more-content.jsp}. You could have the example configuration
shown below:
\texttt{more-content.jsp}:
\begin{verbatim}
<%@ page import="com.liferay.portal.kernel.util.ParamUtil" %>
<%
String answer = ParamUtil.getString(request, "answer");
%>
The answer to life, the universe and everything is <%= answer %>.
\end{verbatim}
Then in \texttt{my-app.jsp}, you can include \texttt{more-content.jsp}
and set the value of the \texttt{answer} parameter:
\begin{verbatim}
\end{verbatim}
This results in the following output in \texttt{my-app.jsp}:
\begin{verbatim}
The answer to life, the universe and everything is 42.
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:param\textgreater{}} tag to set
parameters for included JSPs. You can use this approach to include
common reusable pieces of code in your apps.
\section{Related Topics}\label{related-topics-18}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-include}{Using
the Liferay Util Include Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-body-top}{Using
the Liferay Util Body Top Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-chart-taglib-in-your-portlets}{Using
the Chart Taglib}
\end{itemize}
\chapter{Using Liferay Util Whitespace
Remover}\label{using-liferay-util-whitespace-remover}
The whitespace remover tag removes line breaks and tabs from code blocks
included between the opening and closing of the tag. Below is an example
configuration for the
\texttt{\textless{}liferay-util:whitespace-remover\textgreater{}} tag:
with remover:
\begin{verbatim}
Here is some text with tabs.
\end{verbatim}
result:
\begin{verbatim}
Here is some text withtabs.
\end{verbatim}
Now you know how to use the
\texttt{\textless{}liferay-util:whitespace-remover\textgreater{}} tag to
ensure that your code formatting is consistent.
\section{Related Topics}\label{related-topics-19}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-param}{Using
the Liferay Util Param Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-util-buffer}{Using
the Liferay Util Buffer Tag}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-aui-taglibs}{Using
the AUI Taglib}
\end{itemize}
\chapter{Using the Clay Taglib in Your
portlets}\label{using-the-clay-taglib-in-your-portlets}
The Liferay Clay tag library provides a set of tags for creating
\href{https://clayui.com/}{Clay} UI components in your app.
\noindent\hrulefill
\textbf{Note:} AUI taglibs are deprecated as of 7.0. We recommend that
you use Clay taglibs to avoid future compatibility issues.
\noindent\hrulefill
To use the Clay taglib in your apps, add the following declaration to
your JSP:
\begin{verbatim}
<%@ taglib prefix="clay" uri="http://liferay.com/tld/clay" %>
\end{verbatim}
The Liferay Clay taglib is also available via a macro for your
FreeMarker theme templates and web content templates. Follow this
syntax:
\begin{verbatim}
<@clay["tag-name"] attribute="string value" attribute=10 />
\end{verbatim}
Clay taglibs provide the following UI components for your apps:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-alerts}{Alerts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-badges}{Badges}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-buttons}{Buttons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-cards}{Cards}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-dropdown-menus-and-action-menus}{Dropdown
Menus and Action Menus}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-form-elements}{Form
Elements}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Icons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links}{Labels
and links}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-management-toolbar}{Management
Toolbar}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-navigation-bars}{Navigation
Bars}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-progress-bars}{Progress
Bars}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-stickers}{Stickers}
\end{itemize}
This section covers how to create these components with the Clay
taglibs. Each article contains a set of clay component examples along
with a screenshot of the resulting UI.
\chapter{Clay Alerts}\label{clay-alerts}
Clay alerts come in two types: embedded and stripe. Both types, along
with several examples of each, are shown below.
\section{Embedded Alerts}\label{embedded-alerts}
Embedded alerts are usually used inside forms. The element that contains
it determines an embedded alert's width. The close action is not
required for embedded alerts. The following embedded alerts can be
created with Clay taglibs:
Danger alert (embedded):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-danger.png}
\caption{The danger alert notifies the user of an error or issue.}
\end{figure}
Success alert (embedded):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-success.png}
\caption{The success alert notifies the user when an action is
successful.}
\end{figure}
Info alert (embedded):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-info.png}
\caption{The info alert displays general information to the user.}
\end{figure}
Warning alert (embedded):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-warning.png}
\caption{The warning alert displays a warning message to the user.}
\end{figure}
\section{Stripe Alerts}\label{stripe-alerts}
Stripe alerts are placed below the last navigation element (either the
header or the navigation bar), and they usually appear on \emph{Save}
action, communicating the status of the action once received from the
server. Unlike embedded alerts, stripe alerts require the close action.
A stripe alert is always the full width of the container and pushes all
the content below it. The following stripe alerts can be created with
Clay taglibs:
Danger alert (stripe):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-danger-stripe.png}
\caption{The danger striped alert notifies the user that an action has
failed.}
\end{figure}
Success alert (stripe):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-success-stripe.png}
\caption{The success striped alert notifies the user that an action has
completed successfully.}
\end{figure}
Info alert (stripe):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-info-stripe.png}
\caption{The info striped alert displays general information about an
action to the user.}
\end{figure}
Warning alert (stripe):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-alert-warning-stripe.png}
\caption{The warning striped alert warns the user about an action.}
\end{figure}
Now you know how to alert users!
\section{Related Topics}\label{related-topics-20}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-buttons}{Clay
Buttons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-form-elements}{Clay
Form Elements}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links}{Clay
Labels and Links}
\end{itemize}
\chapter{Clay Badges}\label{clay-badges}
Badges help highlight important information such as notifications or new
and unread messages. Badges have circular borders and are only used to
specify a number. This covers the different types of Clay badges you can
add to your app.
\section{Badge Types}\label{badge-types}
The following badge styles are available:
Primary badge:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-badge-primary.png}
\caption{A primary badge is bright blue, commanding attention like the
primary button of a form.}
\end{figure}
Secondary badge:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-badge-secondary.png}
\caption{A secondary badge is light-grey and draws less focus than a
primary button.}
\end{figure}
Info badge:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-badge-info.png}
\caption{A info badge is dark blue and meant for numbers related to
general information.}
\end{figure}
Error badge:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-badge-error.png}
\caption{An error badge displays numbers related to an error.}
\end{figure}
Success badge:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-badge-success.png}
\caption{A success badge displays numbers related to a successful
action.}
\end{figure}
Warning badge:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-badge-warning.png}
\caption{A warning badge displays numbers related to warnings that
should be addressed.}
\end{figure}
Now you know how to use badges to keep track of values in your app.
\section{Related Topics}\label{related-topics-21}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links}{Clay
Labels and Links}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-cards}{Clay Cards}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-stickers}{Clay
Stickers}
\end{itemize}
\chapter{Clay Buttons}\label{clay-buttons}
Buttons come in several types and variations. This tutorial covers the
different styles and variations of buttons you can create with the Clay
taglibs.
\section{Types}\label{types}
\textbf{Primary button:} Used for the most important actions. Two
primary buttons should not be together or near each other.
Primary button with label:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-primary.png}
\caption{A primary button is bright blue, grabbing the user's
attention.}
\end{figure}
\textbf{Secondary button:} Used for secondary actions. There can be
multiple secondary buttons together or near each other.
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-secondary.png}
\caption{A secondary button draws less attention than a primary button
and is meant for secondary actions.}
\end{figure}
\textbf{Borderless button:} Used in cases such as toolbars where the
secondary button would be too heavy for the design. This keeps the
design clean.
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-borderless.png}
\caption{Borderless buttons remove the dark outline from the button.}
\end{figure}
\textbf{Link button:} Used for Cancel actions.
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-link.png}
\caption{You can also turn buttons into links.}
\end{figure}
You can use labels or icons for your buttons. Below is an example of a
Primary button with an icon:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-primary-icon.png}
\caption{Buttons can also display icons.}
\end{figure}
You can disable a button by adding the \texttt{disabled} attribute:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-primary-disabled.png}
\caption{Buttons can be disabled if you don't want the user to interact
with them.}
\end{figure}
\section{Variations}\label{variations}
Button with icon and text:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-icon-text.png}
\caption{Buttons can display both icons and text.}
\end{figure}
Button with monospaced text:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-monospaced.png}
\caption{Buttons can display monospaced text.}
\end{figure}
Block level button:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-block-level.png}
\caption{Block level buttons span the entire width of the container.}
\end{figure}
Plus button:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-plus.png}
\caption{: A plus button is used for add actions in an app.}
\end{figure}
Action button:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-button-action.png}
\caption{: An action button is used to display actions menus.}
\end{figure}
\section{Related Topics}\label{related-topics-22}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-alerts}{Clay Alerts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-buttons}{Clay
Buttons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links}{Clay
Labels and Links}
\end{itemize}
\chapter{Clay Cards}\label{clay-cards}
Cards visually represent data. Use them for images, document libraries,
user profiles and more. There are four main types of Cards:
\begin{itemize}
\tightlist
\item
Image Cards
\item
File Cards
\item
User Cards
\item
Horizontal Cards
\end{itemize}
Each of these types is covered below.
\section{Image Cards}\label{image-cards}
Image Cards are used for image/document galleries.
Image Card:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-image-card.png}
\caption{Image Cards display images and documents.}
\end{figure}
Image Card with icon:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-image-card-icon.png}
\caption{Image Cards can also display icons instead of images.}
\end{figure}
Image Card empty:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-image-card-empty.png}
\caption{Cards can also display nothing.}
\end{figure}
Cards can also contain file types. Specify the file type with the
\texttt{filetype} attribute:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-image-card-file-type.png}
\caption{Cards can also contain file types.}
\end{figure}
Include the \texttt{labels} attribute to add a label to a Card:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-image-card-icon-label.png}
\caption{You can include labels in Cards.}
\end{figure}
Include the \texttt{selectable} attribute to make cards selectable
(include a checkbox):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-image-card-icon-selectable.png}
\caption{Cards can be selectable.}
\end{figure}
\section{File Cards}\label{file-cards}
File Cards display an icon of the file's type. They represent file types
other than image files (i.e.~PDF, MP3, DOC, etc.).
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-file-card.png}
\caption{File Cards display file type icons.}
\end{figure}
You can optionally use the \texttt{labelStylesMap} attribute to pass a
\texttt{HashMap} of multiple labels, as shown above.
The example below specifies a list \texttt{icon} instead of the default
file icon:
\begin{verbatim}
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} The full list of available Liferay icons can be found on
the \href{https://clayui.com/docs/components/icon.html}{Clay CSS
website}.
\noindent\hrulefill
\section{User Cards}\label{user-cards}
User Cards display user profile images or the initials of the user's
name or name+surname.
User Card with initials:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-user-card-initial.png}
\caption{User Cards can display a user's initials.}
\end{figure}
User Card with profile image:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-user-card-profile-image.png}
\caption{A User Card can also display a profile image.}
\end{figure}
\section{Horizontal Cards}\label{horizontal-cards}
Horizontal Cards represent folders and can have the same amount of
information as other Cards. The key difference is that horizontal Cards
let you remove the image portion of the Card, since only the folder icon
is required.
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-horizontal-card.png}
\caption{: Horizontal Cards are good for displaying folders.}
\end{figure}
Now you know how to use Cards in your UI to display information in your
apps.
\section{Related Topics}\label{related-topics-23}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-badges}{Clay Badges}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links}{Clay
Labels and Links}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-stickers}{Clay
Stickers}
\end{itemize}
\chapter{Clay Dropdown Menus and Action
Menus}\label{clay-dropdown-menus-and-action-menus}
You can add dropdown menus to your app via the
\texttt{clay:dropdown-menu} and \texttt{clay:actions-menu} taglibs. The
Clay taglibs provide several menu variations for you to choose. Both
taglibs with several examples are shown below.
\section{Dropdown Menus}\label{dropdown-menus}
Basic dropdown menu:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-dropdown-basic.png}
\caption{Clay taglibs provide everything you need to add dropdown menus
to your app.}
\end{figure}
The dropdown menu's items are defined in its Java
class--\texttt{dropdownDisplayContext} in this case. Menu items are
\texttt{NavigationItem} objects. You can disable menu items with the
\texttt{setDisabled(true)} method and make a menu item active with the
\texttt{setActive(true)} method. The \texttt{href} attribute is set with
the \texttt{setHref()} method, and labels are defined with the
\texttt{setLabel()} method. Here's an example implementation of the
\texttt{dropdownDisplayContext} class:
\begin{verbatim}
if (_defaultDropdownItems != null) {
return _defaultDropdownItems;
}
_defaultDropdownItems = new ArrayList<>();
for (int i = 0; i < 4; i++) {
NavigationItem navigationItem = new NavigationItem();
if (i == 1) {
navigationItem.setDisabled(true);
}
else if (i == 2) {
navigationItem.setActive(true);
}
navigationItem.setHref("#" + i);
navigationItem.setLabel("Option " + i);
_defaultDropdownItems.add(navigationItem);
}
return _defaultDropdownItems;
}
\end{verbatim}
You can organize menu items into groups by setting the
\texttt{NavigationItem}'s type to \texttt{TYPE\_GROUP} and nesting the
items in separate \texttt{ArrayList}s. You can add a horizontal
separator to separate the groups visually with the
\texttt{setSeparator(true)} method. Below is a code snippet from the
\texttt{dropdownsDisplayContext} class:
\begin{verbatim}
group1NavigationItem.setSeparator(true);
group1NavigationItem.setType(NavigationItem.TYPE_GROUP);
\end{verbatim}
Corresponding taglib:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-dropdown-group.png}
\caption{You can organize dropdown menu items into groups.}
\end{figure}
You can also add inputs to dropdown menus. To add an input to a dropdown
menu, set the input's type with the \texttt{setType()} method
(e.g.~\texttt{NavigationItem.TYPE\_CHECKBOX}), its name with the
\texttt{setInputName()} method, and its value with the
\texttt{setInputValue()} method. Here's an example implementation:
\begin{verbatim}
navigationItem.setInputName("checkbox" + i);
navigationItem.setInputValue("checkboxValue" + i);
navigationItem.setLabel("Group 1 - Option " + i);
navigationItem.setType(NavigationItem.TYPE_CHECKBOX);
\end{verbatim}
Corresponding taglib:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-dropdown-input.png}
\caption{Inputs can be included in dropdown menus.}
\end{figure}
Menu items can also contain icons. To add an icon to a menu item, use
the \texttt{setIcon()} method. Below is an example:
\begin{verbatim}
navigationItem.setIcon("check-circle-full")
\end{verbatim}
Corresponding taglib:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-dropdown-icons.png}
\caption{Icons can be included in dropdown menus.}
\end{figure}
\section{Actions Menus}\label{actions-menus}
Basic actions menu:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-dropdown-actions.png}
\caption{You can also create Actions menus with Clay taglibs.}
\end{figure}
An actions menu can also display help text to the user:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-dropdown-actions-help.png}
\caption{You can provide help text in Actions menus.}
\end{figure}
Clay taglibs make it easy to add dropdown menus and action menus to your
apps.
\section{Related Topics}\label{related-topics-24}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-form-elements}{Clay
Form Elements}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-navigation-bars}{Clay
Navigation Bars}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-progress-bars}{Clay
Progress Bars}
\end{itemize}
\chapter{Clay Form Elements}\label{clay-form-elements}
The Liferay Clay tag library provides several tags for creating form
elements. An example of each tag is shown below.
\section{Checkbox}\label{checkbox}
Checkboxes give the user a true or false input.
\begin{verbatim}
\end{verbatim}
Attributes:
\textbf{checked:} Whether the checkbox is checked
\textbf{disabled:} Whether the checkbox is enabled
\textbf{hideLabel:} Whether to display the checkbox label
\textbf{indeterminate:} Checkbox variable for multiple selection
\textbf{label:} The checkbox's label
\textbf{name:} The checkbox's name
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-form-checkbox.png}
\caption{Clay taglibs provide checkboxes.}
\end{figure}
\section{Radio}\label{radio}
A radio button lets the user select one choice from a set of options in
a form.
\begin{verbatim}
\end{verbatim}
Attributes:
\textbf{checked:} Whether the radio button is checked
\textbf{hideLabel:} Whether to display the radio button label
\textbf{disabled:} Whether the radio button is enabled
\textbf{label:} The radio button's label
\textbf{name:} The radio button's name
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-form-radio-button.png}
\caption{Clay taglibs provide radio buttons.}
\end{figure}
\section{Selector}\label{selector}
A selector gives the user a select box with a set of options to choose
from.
The Java scriplet below creates eight dummy options for the selector:
\begin{verbatim}
<%
List> options = new ArrayList<>();
for (int i = 0; i < 8; i++) {
Map option = new HashMap<>();
option.put("label", "Sample " + i);
option.put("value", i);
options.add(option);
}
%>
\end{verbatim}
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-form-selector.png}
\caption{Clay taglibs provide select boxes.}
\end{figure}
If you want let users select multiple options at once, set the
\texttt{multiple} attribute to \texttt{true}:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-form-selector-multiple.png}
\caption{You can let users select multiple options from the select
menu.}
\end{figure}
Attributes:
\textbf{disabled:} Whether the selector is enabled \textbf{label:} The
selector's label \textbf{multiple:} Whether multiple options can be
selected \textbf{name:} The selector's name
Now you know how to use Clay taglibs to add common form elements to your
app!
\section{Related Topics}\label{related-topics-25}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-buttons}{Clay
Buttons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links}{Clay
Labels and Links}
\end{itemize}
\chapter{Clay Icons}\label{clay-icons}
The Liferay Clay taglibs provide several icons that you can use in your
apps. Use the \texttt{clay:icon} tag and specify the icon with the
\texttt{symbol} attribute:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-icon-folder.png}
\caption{You can include icons in your app with the Clay taglib.}
\end{figure}
The full list of icons is shown below:
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-icon-library.png}
\caption{The Clay taglib gives you access to several Liferay DXP icons.}
\end{figure}
The Liferay Clay taglibs also provide a set of language flag icons that
you can use in your app. The full list of language flags is shown below:
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-icon-language-flags.png}
\caption{You can include language flags in your apps.}
\end{figure}
\section{Related Topics}\label{related-topics-26}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-badges}{Clay Badges}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-stickers}{Clay
Stickers}
\item
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-clay-icons-in-a-theme}{Using
Clay Icons in a Theme}
\end{itemize}
\chapter{Clay Labels and Links}\label{clay-labels-and-links}
Liferay Clay taglibs provide tags for creating labels and links in your
app. Both of these elements are covered below.
\section{Labels}\label{labels}
The Liferay Clay taglibs provide a few different labels for your app.
Use the \texttt{clay:label} tag to add a label to your app. You can
create color-coded labels, removable labels, and labels that contain
links. The sections below demonstrate all of these options.
\section{Color-coded Labels}\label{color-coded-labels}
The Liferay Clay labels come in four different colors: dark-blue for
info, light-gray for status, orange for pending, red for rejected, and
green for approved.
Info labels are dark-blue, and since they stand out a bit more than
status labels, they are best for conveying general information. To use
an info label, set the \texttt{style} attribute to \texttt{info}:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-label-info.png}
\caption{Info labels convey general information.}
\end{figure}
Status labels are light-gray, and due to their neutral color, they are
best for conveying basic information. Status labels are the default
label and therefore require no \texttt{style} attribute:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-label-status.png}
\caption{Status labels are the least flashy and best for displaying
basic information.}
\end{figure}
Warning labels are orange, and due to their color, they are best for
conveying a warning message. To use a warning label, set the
\texttt{style} attribute to \texttt{warning}:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-label-warning.png}
\caption{Warning labels notify the user of issues, but nothing app
breaking.}
\end{figure}
Danger labels are red and indicate that something is wrong or has
failed. To use a danger label, set the \texttt{style} attribute to
\texttt{danger}:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-label-danger.png}
\caption{Danger labels convey a sense of urgency that must be
addressed.}
\end{figure}
Success labels are green and indicate that something has completed
successfully. To use a success label, set the \texttt{style} attribute
to \texttt{success}:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-label-success.png}
\caption{Success labels indicate a successful action.}
\end{figure}
Labels can also be bigger. Set the \texttt{size} attribute to
\texttt{lg} to display large labels:
\begin{verbatim}
\end{verbatim}
\section{Removable Labels}\label{removable-labels}
If you want to let a user close a label (e.g.~a temporary notification),
you can make the label removable by setting the \texttt{closeable}
attribute to \texttt{true}.
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-label-removable.png}
\caption{Labels can be removable.}
\end{figure}
\section{Labels with Links}\label{labels-with-links}
You can make a label a link by adding the \texttt{href} attribute to it
just as you would an anchor tag:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-label-link.png}
\caption{Labels can also be links.}
\end{figure}
\section{Links}\label{links}
You can add traditional hyperlinks to your app with the
\texttt{\textless{}clay:link\textgreater{}} tag:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-link.png}
\caption{Clay taglibs also provide link elements.}
\end{figure}
Now you know how to add links and labels to your apps!
\section{Related Topics}\label{related-topics-27}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-badges}{Clay Badges}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-cards}{Clay Cards}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-form-elements}{Clay
Form Elements}
\end{itemize}
\chapter{Clay Management Toolbar}\label{clay-management-toolbar}
The Management Toolbar gives administrators control over search
container results in their apps. It lets you filter, sort, and choose a
view type for search results, so you can quickly identify the document,
web content, asset entry, or whatever you're looking for. The Management
Toolbar is fully customizable, so you can implement all the controls or
just the ones your app requires.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar.png}
\caption{The Management ToolBar lets the user customize how the app
displays content.}
\end{figure}
To create a management toolbar, use the \texttt{clay:management-toolbar}
taglib. The toolbar contains a few key sections. Each section is grouped
and configured using different attributes. These attributes are
described in more detail below.
\section{Using a Display Context to Configure the Management
Toolbar}\label{using-a-display-context-to-configure-the-management-toolbar}
If you're using a Display Context---a separate class to configure your
display options for your management toolbar---to define all or some of
the configuration options for the toolbar, you can specify the Display
Context with the \texttt{displayContext} attribute. An example is shown
below:
\begin{verbatim}
\end{verbatim}
You can see an example use case of a Display Context in
\href{/docs/7-2/frameworks/-/knowledge_base/f/filtering-and-sorting-items-with-the-management-toolbar}{Filtering
and Sorting Items with the Management Toolbar}. A Display Context is not
required for a management toolbar's configuration. You can provide as
much or as little of the configuration options for your management
toolbar through the Display Context as you like.
\section{Checkbox and Actions}\label{checkbox-and-actions}
The \texttt{actionItems}, \texttt{searchContainerId},
\texttt{selectable}, and \texttt{totalItems} attributes let you include
a checkbox in the toolbar to select all search container results and run
bulk actions on them. Actions and total items display when an individual
result is checked, or when the master checkbox is checked in the
toolbar.
\texttt{actionItems}: The list of dropdown items to display when a
result is checked or the master checkbox in the Management Toolbar is
checked. You can select multiple results between pages. The Management
Toolbar keeps track of the number of selected results for you.
\texttt{searchContainerId}: The ID of the search container connected to
the Management Toolbar
\texttt{selectable}: Whether to include a checkbox in the Management
Toolbar
\texttt{totalItems}: The total number of items across pagination. This
number displays when one or multiple items are selected.
An example configuration is shown below:
\begin{verbatim}
actionItems="<%=
new JSPDropdownItemList(pageContext) {
{
add(
dropdownItem -> {
dropdownItem.setHref("#edit");
dropdownItem.setLabel("Edit");
});
add(
dropdownItem -> {
dropdownItem.setHref("#download");
dropdownItem.setIcon("download");
dropdownItem.setLabel("Download");
dropdownItem.setQuickAction(true);
});
add(
dropdownItem -> {
dropdownItem.setHref("#delete");
dropdownItem.setLabel("Delete");
dropdownItem.setIcon("trash");
dropdownItem.setQuickAction(true);
});
}
}
%>"
\end{verbatim}
Action items are listed in the Actions menu, along with the number of
items selected across pagination.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-actions.png}
\caption{Actions are also listed in the Management Toolbar's dropdown
menu when an item, multiple items, or the master checkbox is checked.}
\end{figure}
If an action has an icon specified, such as the Delete and Download
actions in the example above, the icon is displayed next to the action
menu as well.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-selectable.png}
\caption{The Management Toolbar keeps track of the results selected and
displays the actions to execute on them.}
\end{figure}
\section{Filtering and Sorting Search
Results}\label{filtering-and-sorting-search-results}
The \texttt{filterItems}, \texttt{sortingOrder}, and \texttt{sortingURL}
attributes let you filter and sort search container results. Filtering
and sorting are grouped together in one convenient dropdown menu.
\texttt{filterItems}: Sets the search container's filtering options.
This filter should be included in all control panel applications.
Filtering options can include sort criteria, sort ordering, and more.
\texttt{filterLabelItems}: Sets the search container's filter labels to
display. This lets the user know which filters are currently applied.
\texttt{sortingOrder}: The current sorting order: ascending or
descending.
\texttt{sortingURL}: The URL to change the sorting order
The example below adds two filter options and two sorting options:
\begin{verbatim}
filterItems="<%=
new DropdownItemList(_request) {
{
addGroup(
dropdownGroupItem -> {
dropdownGroupItem.setDropdownItemList(
new DropdownItemList(_request) {
{
add(
dropdownItem -> {
dropdownItem.setHref("#1");
dropdownItem.setLabel("Filter 1");
});
add(
dropdownItem -> {
dropdownItem.setHref("#2");
dropdownItem.setLabel("Filter 2");
});
}
}
);
dropdownGroupItem.setLabel("Filter By");
});
addGroup(
dropdownGroupItem -> {
dropdownGroupItem.setDropdownItemList(
new DropdownItemList(_request) {
{
add(
dropdownItem -> {
dropdownItem.setHref("#3");
dropdownItem.setLabel("Order 1");
});
add(
dropdownItem -> {
dropdownItem.setHref("#4");
dropdownItem.setLabel("Order 2");
});
}
}
);
dropdownGroupItem.setLabel("Order By");
});
}
}
%>"
\end{verbatim}
\begin{verbatim}
filterLabelItems="<%=
new LabelItemList() {
{
add(
labelItem -> {
labelItem.setLabel("Filter 1");
});
add(
labelItem -> {
labelItem.setLabel("Filter 2");
});
}
};
%>"
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-filter-and-sort.png}
\caption{You can also sort and filter search container results.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-filter-label-items.jpg}
\caption{You can also sort and filter search container results.}
\end{figure}
\section{Search Form}\label{search-form}
The \texttt{clearResultsURL}, \texttt{searchActionURL},
\texttt{searchFormName}, \texttt{searchInputName}, and
\texttt{searchValue} attributes let you configure the search form. The
main portion of the Management Toolbar is reserved for the search form.
\texttt{clearResultsURL}: The URL to reset the search
\texttt{searchActionURL}: The action URL to send the search form
\texttt{searchFormName}: The search form's name
\texttt{searchInputName}: The search input's name
\texttt{searchValue}: The search input's value
An example configuration is shown below:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-search-form.png}
\caption{The search form comprises most of the Management Toolbar,
letting users search through the search container results.}
\end{figure}
\section{Info Panel}\label{info-panel}
The \texttt{infoPanelId} and \texttt{showInfoButton} attributes let you
add a retractable sidebar panel that displays additional information
related to a search container result.
\texttt{infoPanelId}: The ID of the info panel to toggle
\texttt{showInfoButton}: Whether to show the info button
In the example configuration below, the \texttt{showInfoButton}
attribute is provided in the Display Context---specified with the
\texttt{displayContext} attribute---and the \texttt{infoPanelId} is
explicitly set in the JSP:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-info-panel.png}
\caption{The info panel keeps your UI clutter-free.}
\end{figure}
\section{View Types}\label{view-types}
The \texttt{viewTypes} attribute specifies the display options for the
search container results. There are three display options to choose
from:
\textbf{Cards:} Displays search result columns on a horizontal or
vertical card.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-view-type-card.png}
\caption{The Management Toolbar's icon display view gives a quick
summary of the content's description and status.}
\end{figure}
\textbf{List:} Displays a detailed description along with summarized
details for the search result columns.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-view-type-list.png}
\caption{The Management Toolbar's List view type gives the content's
full description.}
\end{figure}
\textbf{Table:} The default view. Lists the search result columns from
left to right.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-view-type-table.png}
\caption{: The Management Toolbar's Table view type list the content's
information in individual columns.}
\end{figure}
An example configuration is shown below:
\begin{verbatim}
viewTypes="<%=
new JSPViewTypeItemList(pageContext, baseURL, selectedType) {
{
addCardViewTypeItem(
viewTypeItem -> {
viewTypeItem.setActive(true);
viewTypeItem.setLabel("Card");
});
addListViewTypeItem(
viewTypeItem -> {
viewTypeItem.setLabel("List");
});
addTableViewTypeItem(
viewTypeItem -> {
viewTypeItem.setLabel("Table");
});
}
}
%>"
\end{verbatim}
While the example above shows how to configure the view types in the
JSP, you must also
\href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-the-view-types}{specify
when to use each view type}.
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-view-types.png}
\caption{: The Management Toolbar offers three view type options.}
\end{figure}
\section{Creation Menu}\label{creation-menu}
The \texttt{creationMenu} attribute creates an add menu button for one
or multiple items. It's used for creating new entities (e.g.~a new blog
entry).
Use the \texttt{addPrimaryDropdownItem()} method to add the top level
items to the dropdown menu, or use the
\texttt{addFavoriteDropdownItem()} method to add secondary items to the
dropdown menu.
The example configuration below adds two primary creation menu items and
two secondary creation menu items:
\begin{verbatim}
creationMenu="<%=
new JSPCreationMenu(pageContext) {
{
addPrimaryDropdownItem(
dropdownItem -> {
dropdownItem.setHref("#1");
dropdownItem.setLabel("Sample 1");
});
addPrimaryDropdownItem(
dropdownItem -> {
dropdownItem.setHref("#2");
dropdownItem.setLabel("Sample 2");
});
addFavoriteDropdownItem(
dropdownItem -> {
dropdownItem.setHref("#3");
dropdownItem.setLabel("Favorite 1");
});
addFavoriteDropdownItem(
dropdownItem -> {
dropdownItem.setHref("#4");
dropdownItem.setLabel("Other item");
});
}
};
%>"
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-management-toolbar-creation-menu.png}
\caption{: The Management Toolbar lets you optionally add a Creation
Menu for creating new entities.}
\end{figure}
\section{Related Topics}\label{related-topics-28}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-dropdown-menus-and-action-menus}{Clay
Dropdown Menus and Action Menus}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-navigation-bars}{Clay
Navigation Bars}
\end{itemize}
\chapter{Clay Navigation Bars}\label{clay-navigation-bars}
Similar to dropdown menus, navigation bars display a list of navigation
items. The key difference is navigation bars are displayed in a
horizontal bar with all navigation items visible at all times. The
navigation bar also indicates the active navigation item with an
underline. Navigation bars come in two styles: white background with
dark-grey text (default) and dark-grey background with white text
(inverted).
Default navigation bar:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-nav-bars.png}
\caption{You can include navigation bars in your apps.}
\end{figure}
Inverted navigation bar (set \texttt{inverted} attribute to
\texttt{true}):
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-nav-bars-inverted.png}
\caption{Navigation bars can be inverted if you prefer.}
\end{figure}
\section{Related Topics}\label{related-topics-29}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-dropdown-menus-and-action-menus}{Clay
Dropdown Menus and Action Menus}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-form-elements}{Clay
Form Elements}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-progress-bars}{Clay
Progress Bars}
\end{itemize}
\chapter{Clay Progress Bars}\label{clay-progress-bars}
You can add progress bars to your app with the \texttt{clay:progressbar}
tag. These indicate the completion percentage of a task and come in
three status styles: \texttt{default} (blue), \texttt{warning} (red),
and \texttt{complete} (green with checkmark). You can provide a minimum
value (\texttt{minValue}) and a maximum value (\texttt{maxValue}).
Default progress bar:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-progress-bar.png}
\caption{You can include progress bars in your apps.}
\end{figure}
Warning progress bar:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-progress-bar-warning.png}
\caption{warning progress bars indicate that the progress has not
completed due to an error.}
\end{figure}
Complete progress bar:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-progress-bar-complete.png}
\caption{The complete progress bar indicates the progress is complete.}
\end{figure}
Clay taglibs make it easy to track progress in your apps.
\section{Related Topics}\label{related-topics-30}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-dropdown-menus-and-action-menus}{Clay
Dropdown Menus and Action Menus}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-navigation-bars}{Clay
Navigation Bars}
\end{itemize}
\chapter{Clay Stickers}\label{clay-stickers}
Whereas badges display numbers and labels display short information,
stickers are small visual indicators of the content (usually the content
type). They can include a small label or a Liferay icon, and they come
in two shapes: circle and square.
Square sticker with label:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-sticker-square-label.png}
\caption{You can include stickers in your apps.}
\end{figure}
Square sticker with icon:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-sticker-square-icon.png}
\caption{Stickers can include icons.}
\end{figure}
Circle sticker:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-sticker-round.png}
\caption{You can also have circle stickers.}
\end{figure}
Stickers can be positioned in any corner of a div. Indicate their
position with the \texttt{position} attribute: \texttt{top-left},
\texttt{bottom-left}, \texttt{top-right}, or \texttt{bottom-right}:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/clay-taglib-sticker-position.png}
\caption{You can specify the position of the sticker within a
container.}
\end{figure}
Now you know how to use Clay stickers in your app!
\section{Related Topics}\label{related-topics-31}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-badges}{Clay Badges}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-cards}{Clay Cards}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/clay-icons}{Clay Icons}
\end{itemize}
\chapter{Using the Chart Taglib in Your
Portlets}\label{using-the-chart-taglib-in-your-portlets}
Lines, splines, bars, pies and more, the Chart tag Library provides
everything you need to model data. Each taglib gives you access to the
corresponding
\href{https://github.com/liferay/clay/tree/2.x-stable/packages/clay-charts/src}{Clay
component}. These components contain the default configuration for the
UI.
To use the Chart taglib in your apps, add the following declaration to
your JSP:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
\end{verbatim}
This section covers the types of charts you can create with the Chart
taglibs. Each article contains a set of chart examples along with sample
Java data and a figure displaying the rendered results.
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-sample-portlet.png}
\caption{You can create many different types of charts with the chart
taglibs.}
\end{figure}
\chapter{Bar Charts}\label{bar-charts}
Bar charts contain multiple sets of data. A bar chart models the data in
bars. Each data series (created with the \texttt{addColumns()} method)
is defined with a new instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/MultiValueColumn.html}{\texttt{MultiValueColumn}
object}, which takes an ID and a set of values. Follow these steps to
configure your portlet to use bar charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{BarChartConfig} and
\texttt{MultiValueColumn} classes into your bundle's \texttt{init.jsp}
file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.point.bar.BarChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.MultiValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
BarChartConfig _barChartConfig = new BarChartConfig();
_barChartConfig.addColumns(
new MultiValueColumn("data1", 100, 20, 30),
new MultiValueColumn("data2", 20, 70, 100)
);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_barChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-bar.png}
\caption{A bar chart models the data in bars.}
\end{figure}
Awesome! Now you know how to create bar charts for your apps.
\section{Related Topics}\label{related-topics-32}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/line-charts}{Line Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/donut-charts}{Donut
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/combination-charts}{Combination
Charts}
\end{itemize}
\chapter{Line Charts}\label{line-charts}
Line charts contain multiple sets of data. A Line chart displays the
data linearly. Each data series (created with the \texttt{addColumns()}
method) is defined with a new instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/MultiValueColumn.html}{\texttt{MultiValueColumn}
object}, which takes an ID and a set of values. Follow these steps to
configure your portlet to use line charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{LineChartConfig} and
\texttt{MultiValueColumn} classes into your bundle's \texttt{init.jsp}
file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.point.line.LineChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.MultiValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
LineChartConfig _lineChartConfig = new LineChartConfig();
_lineChartConfig.addColumns(
new MultiValueColumn("data1", 100, 20, 30),
new MultiValueColumn("data2", 20, 70, 100)
);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_lineChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-line.png}
\caption{A Line chart displays the data linearly.}
\end{figure}
Awesome! Now you know how to create line charts for your apps.
\section{Related Topics}\label{related-topics-33}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/spline-charts}{Spline
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/step-charts}{Step Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/predictive-charts}{Predictive
Charts}
\end{itemize}
\chapter{Scatter Charts}\label{scatter-charts}
Scatter charts contain multiple sets of data. A scatter chart models the
data as individual points. Each data series (created with the
\texttt{addColumns()} method) is defined with a new instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/MultiValueColumn.html}{\texttt{MultiValueColumn}
object}, which takes an ID and a set of values. Follow these steps to
configure your portlet to use scatter charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{ScatterChartConfig} and
\texttt{MultiValueColumn} classes into your bundle's \texttt{init.jsp}
file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.point.scatter.ScatterChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.MultiValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
ScatterChartConfig _scatterChartConfig = new ScatterChartConfig();
_scatterChartConfig.addColumns(
new MultiValueColumn("data1", 100, 20, 30),
new MultiValueColumn("data2", 20, 70, 100));
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_scatterChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-scatter.png}
\caption{A scatter chart models the data as individual points.}
\end{figure}
Awesome! Now you know how to create scatter charts for your apps.
\section{Related Topics}\label{related-topics-34}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/line-charts}{Line Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/step-charts}{Step Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/predictive-charts}{Predictive
Charts}
\end{itemize}
\chapter{Spline Charts}\label{spline-charts}
Spline charts contain multiple sets of data. A spline chart connects
points of data with a smooth curve. Each data series (created with the
\texttt{addColumns()} method) is defined with a new instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/MultiValueColumn.html}{\texttt{MultiValueColumn}
object}, which takes an ID and a set of values. Follow these steps to
configure your portlet to use spline charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{SplineChartConfig} and
\texttt{MultiValueColumn} classes into your bundle's \texttt{init.jsp}
file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.point.spline.SplineChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.MultiValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
SplineChartConfig _splineChartConfig = new SplineChartConfig();
_splineChartConfig.addColumns(
new MultiValueColumn("data1", 100, 20, 30),
new MultiValueColumn("data2", 20, 70, 100)
);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_splineChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-spline.png}
\caption{A spline chart connects points of data with a smooth curve.}
\end{figure}
You can also use an area spline chart if you prefer. An area spline
chart highlights the area under the spline curve.
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-area-spline.png}
\caption{An area spline chart highlights the area under the spline
curve.}
\end{figure}
Awesome! Now you know how to create spline charts for your apps.
\section{Related Topics}\label{related-topics-35}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/line-charts}{Line Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/step-charts}{Step Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/scatter-charts}{Scatter
Charts}
\end{itemize}
\chapter{Step Charts}\label{step-charts}
Step charts contain multiple sets of data. A step chart steps between
the points of data, resembling steps. Each data series (created with the
\texttt{addColumns()} method) is defined with a new instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/MultiValueColumn.html}{\texttt{MultiValueColumn}
object}, which takes an ID and a set of values. Follow these steps to
configure your portlet to use step charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{StepChartConfig} and
\texttt{MultiValueColumn} classes into your bundle's \texttt{init.jsp}
file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.point.step.StepChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.MultiValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
StepChartConfig _stepChartConfig = new StepChartConfig();
_stepChartConfig.addColumns(
new MultiValueColumn("data1", 100, 20, 30),
new MultiValueColumn("data2", 20, 70, 100)
);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_stepChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-step.png}
\caption{A step chart steps between the points of data, resembling
steps.}
\end{figure}
You can also use an area step chart if you prefer. An area step chart
highlights the area covered by a step graph.
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-area-step.png}
\caption{An area step chart highlights the area covered by a step
graph.}
\end{figure}
Awesome! Now you know how to create step charts for your apps.
\section{Related Topics}\label{related-topics-36}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/line-charts}{Line Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/spline-charts}{Spline
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/scatter-charts}{Scatter
Charts}
\end{itemize}
\chapter{Combination Charts}\label{combination-charts}
Combination charts have minor differences from other charts. In a
combination chart, you must define the representation type of each data
set: \texttt{AREA}, \texttt{AREA\_SPLINE}, \texttt{AREA\_STEP},
\texttt{BAR}, \texttt{BUBBLE}, \texttt{DONUT}, \texttt{GAUGE},
\texttt{LINE}, \texttt{PIE}, \texttt{SCATTER}, \texttt{SPLINE}, or
\texttt{STEP}. Each data set in a combination chart is an instance of
the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/TypedMultiValueColumn.html}{\texttt{TypedMultiValueColumn}
object}. Each object receives an ID, the representation type, and values
for the data. Follow these steps to configure your portlet to use
combination charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the
\texttt{CombinationChartConfig}, \texttt{MultiValueColumn}, and
\texttt{MultiValueColumn.Type} classes into your bundle's
\texttt{init.jsp} file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.combination.CombinationChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.MultiValueColumn" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.TypedMultiValueColumn.Type" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
CombinationChartConfig _combinationChartConfig =
new CombinationChartConfig();
_combinationChartConfig.addColumns(
new TypedMultiValueColumn(
"data1", Type.BAR, 30, 20, 50, 40, 60, 50),
new TypedMultiValueColumn(
"data2", Type.BAR, 200, 130, 90, 240, 130, 220),
new TypedMultiValueColumn(
"data3", Type.SPLINE, 300, 200, 160, 400, 250, 250),
new TypedMultiValueColumn(
"data4", Type.LINE, 200, 130, 90, 240, 130, 220),
new TypedMultiValueColumn(
"data5", Type.BAR, 130, 120, 150, 140, 160, 150),
new TypedMultiValueColumn(
"data6", Type.AREA, 90, 70, 20, 50, 60, 120)
);
_combinationChartConfig.addGroup("data1", "data2");
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_combinationChartConfig} as
the \texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-combination.png}
\caption{A combination chart displays a variety of data set types.}
\end{figure}
Awesome! Now you know how to create combination charts for your apps.
\section{Related Topics}\label{related-topics-37}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/bar-charts}{Bar Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/line-charts}{Line Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/geomap-charts}{Geomap
Charts}
\end{itemize}
\chapter{Donut Charts}\label{donut-charts}
Donut charts are percentage-based. A donut chart is similar to a pie
chart, but it has a hole in the center. Each data set must be defined as
a new instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/SingleValueColumn.html}{\texttt{SingleValueColumn}
object}. Follow these steps to configure your portlet to use donut
charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{DonutChartConfig} and
\texttt{SingleValueColumn} classes into your bundle's
\texttt{init.jsp} file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.percentage.donut.DonutChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.SingleValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
DonutChartConfig _donutChartConfig = new DonutChartConfig();
_donutChartConfig.addColumns(
new SingleValueColumn("data1", 30),
new SingleValueColumn("data2", 70)
);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_donutChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-donut.png}
\caption{A donut chart is similar to a pie chart, but it has a hole in
the center.}
\end{figure}
Awesome! Now you know how to create donut charts for your apps.
\section{Related Topics}\label{related-topics-38}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/pie-charts}{Pie Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/gauge-charts}{Gauge
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/bar-charts}{Bar Charts}
\end{itemize}
\chapter{Gauge Charts}\label{gauge-charts}
Gauge charts are percentage-based. A gauge chart shows where
percentage-based data falls over a given range. Each data set must be
defined as a new instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/SingleValueColumn.html}{\texttt{SingleValueColumn}
object}. Follow these steps to configure your portlet to use gauge
charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{GaugeChartConfig} and
\texttt{SingleValueColumn} classes into your bundle's
\texttt{init.jsp} file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.gauge.GaugeChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.SingleValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
GaugeChartConfig _gaugeChartConfig = new GaugeChartConfig();
_gaugeChartConfig.addColumn(
new SingleValueColumn("data1", 85.4)
);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_gaugeChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-gauge.png}
\caption{A gauge chart shows where percentage-based data falls over a
given range.}
\end{figure}
Awesome! Now you know how to create gauge charts for your apps.
\section{Related Topics}\label{related-topics-39}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/pie-charts}{Pie Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/donut-charts}{Donut
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/bar-charts}{Bar Charts}
\end{itemize}
\chapter{Pie Charts}\label{pie-charts}
Pie charts are percentage-based. A pie chart models percentage-based
data as individual slices of pie. Each data set must be defined as a new
instance of the
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/SingleValueColumn.html}{\texttt{SingleValueColumn}
object}. Follow these steps to configure your portlet to use pie charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{PieChartConfig} and
\texttt{SingleValueColumn} classes into your bundle's
\texttt{init.jsp} file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.percentage.pie.PieChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.SingleValueColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}:
\begin{verbatim}
<%
PieChartConfig _pieChartConfig = new PieChartConfig();
_pieChartConfig.addColumn(
new SingleValueColumn("data1", 85.4)
);
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_pieChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-pie.png}
\caption{A pie chart models percentage-based data as individual slices
of pie.}
\end{figure}
Awesome! Now you know how to create pie charts for your apps.
\section{Related Topics}\label{related-topics-40}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/donut-charts}{Donut
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/gauge-charts}{Gauge
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/spine-charts}{Spline
Charts}
\end{itemize}
\chapter{Geomap Charts}\label{geomap-charts}
A Geomap Chart lets you visualize data based on geography, given a
specified color range---a lighter color representing a lower rank and a
darker a higher rank usually. The default configuration comes from the
Clay charts
\href{https://github.com/liferay/clay/blob/2.x-stable/packages/clay-charts/src/Geomap.js\#L90-L104}{geomap
component}: which ranges from light-blue (\#b1d4ff) to dark-blue
(\#0065e4) and ranks the geography based on the location's
\texttt{pop\_est} value (specified in the geomap's JSON file).
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-geomap-default.png}
\caption{A Geomap chart displays a heatmap representing the data.}
\end{figure}
Follow these steps to configure your portlet to use geomap charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{GeomapConfig},
\texttt{GeomapColor}, and \texttt{GeomapColorRange} classes into your
bundle's \texttt{init.jsp} file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.geomap.GeomapConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.geomap.GeomapColor" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.geomap.GeomapColorRange" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}.
The colors---a color for minimum and a color for maximum---are
completely configurable, as shown in the second example configuration
below: \texttt{\_geomapConfig2}. Create a new
\texttt{GeomapColorRange} and set the minimum and maximum color values
with the \texttt{setMax()} and \texttt{setMin()} methods. Specify the
highlight color---the color displayed when you mouse over an
area---with the \texttt{setSelected()} method. use the
\texttt{geomapColor.setValue()} method to specify the JSON property to
determine the geomap's ranking. Specify the JSON filepath with the
\texttt{setDataHREF()} method. The example below displays a geomap
based on the length of each location's name:
\begin{verbatim}
<%
GeomapConfig _geomapConfig1 = new GeomapConfig();
GeomapConfig _geomapConfig2 = new GeomapConfig();
GeomapColor geomapColor = new GeomapColor();
GeomapColorRange geomapColorRange = new GeomapColorRange();
geomapColorRange.setMax("#b2150a");
geomapColorRange.setMin("#ee3e32");
geomapColor.setGeomapColorRange(geomapColorRange);
geomapColor.setSelected("#a9615c");
geomapColor.setValue("name_len");
_geomapConfig2.setColor(geomapColor);
StringBuilder sb = new StringBuilder();
sb.append(_portletRequest.getScheme());
sb.append(StringPool.COLON);
sb.append(StringPool.SLASH);
sb.append(StringPool.SLASH);
sb.append(_portletRequest.getServerName());
sb.append(StringPool.COLON);
sb.append(_portletRequest.getServerPort());
sb.append(_portletRequest.getContextPath());
sb.append(StringPool.SLASH);
sb.append("geomap.geo.json");
_geomapConfig1.setDataHREF(sb.toString());
_geomapConfig2.setDataHREF(sb.toString());
%>
\end{verbatim}
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp} along with any styling information for the geomap,
such as the size and margins as shown below:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-geomap-custom.png}
\caption{Geomap charts can be customized to fit the look and feel you
desire.}
\end{figure}
Awesome! Now you know how to create geomap charts for your apps.
\section{Related Topics}\label{related-topics-41}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/bar-charts}{Bar Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/pie-charts}{Pie Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/combination-charts}{Combination
Charts}
\end{itemize}
\chapter{Predictive Charts}\label{predictive-charts}
Predictive charts let you visualize current data along with
predicted/forecasted data within a given value range.
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-predictive-value-range.png}
\caption{Predicted/forecasted data is surrounded by a highlighted area
of possible values.}
\end{figure}
Follow these steps to use predictive charts.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Import the chart taglib along with the \texttt{PredictiveChartConfig}
and \texttt{MixedDataColumn} classes into your bundle's
\texttt{init.jsp} file:
\begin{verbatim}
<%@ taglib prefix="chart" uri="http://liferay.com/tld/chart" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.predictive.PredictiveChartConfig" %>
<%@ page import="com.liferay.frontend.taglib.chart.model.MixedDataColumn" %>
\end{verbatim}
\item
Add the following Java scriptlet to the top of your \texttt{view.jsp}.
Add a
\href{https://docs.liferay.com/portal/7.2-latest/apps/frontend-taglib-2.0.2/javadocs/com/liferay/frontend/taglib/chart/model/MixedDataColumn.html}{\texttt{MixedDataColumn}
object} ---a column that supports both single number values and arrays
of three numbers---for each data series. Single number values define
existing data. Arrays of numbers are used as the prediction/forecast
data and contain three numbers: a minimum value, an estimated value,
and a maximum value. The estimated value is rendered solid and
surrounded by a highlighted area with borders specified by the minimum
and maximum values. This lets you visualize your estimated values,
while also giving you an idea of the possible value ranges. Use the
\texttt{addDataColumn()} method to add each data series:
\begin{verbatim}
<%
private PredictiveChartConfig _predictiveChartConfig = new
PredictiveChartConfig();
MixedDataColumn mixedDataColumn1 = new MixedDataColumn(
"data1", 130, 340, 200, 500, 80, 240, 40,
new Number[] {370, 400, 450}, new Number[] {210, 240, 270},
new Number[] {150, 180, 210}, new Number[] {60, 90, 120},
new Number[] {310, 340, 370}
);
_predictiveChartConfig.addDataColumn(mixedDataColumn1);
MixedDataColumn mixedDataColumn2 = new MixedDataColumn(
"data2", 210, 160, 50, 125, 230, 110, 90,
Arrays.asList(170, 200, 230), Arrays.asList(10, 40, 70),
Arrays.asList(350, 380, 410), Arrays.asList(260, 290, 320),
Arrays.asList(30, 70, 150)
);
_predictiveChartConfig.addDataColumn(mixedDataColumn2);
_predictiveChartConfig.setAxisXTickFormat("%b");
_predictiveChartConfig.setPredictionDate("2018-07-01");
List timeseries = new ArrayList<>();
timeseries.add("2018-01-01");
timeseries.add("2018-02-01");
timeseries.add("2018-03-01");
timeseries.add("2018-04-01");
timeseries.add("2018-05-01");
timeseries.add("2018-06-01");
timeseries.add("2018-07-01");
timeseries.add("2018-08-01");
timeseries.add("2018-09-01");
timeseries.add("2018-10-01");
timeseries.add("2018-11-01");
timeseries.add("2018-12-01");
_predictiveChartConfig.setTimeseries(timeseries);
%>
\end{verbatim}
Predictive charts have these properties:
\textbf{axisXTickFormat:} An optional string which specfies the time
formatting on the X axis. For more information on which formats can be
specified please refer to
\href{https://github.com/d3/d3-time-format/blob/master/README.md\#locale_format}{d3's
time format README}. This value is set using the
\texttt{setAxisXTickFormat()} method.
\textbf{Prediction Date:} A date as a string that represents the point
in the timeline from when the forecast/prediction is shown. This value
is parsed as a Date object in JavaScript and set using the
\texttt{setPredictionDate()} method.
\textbf{Time Series:} A timeline for the data which is displayed on
the X axis of the chart. This value is set as an array of dates
(\texttt{2018-01-01} for example).
\item
Add the \texttt{\textless{}chart\textgreater{}} taglib to the
\texttt{view.jsp}, passing the \texttt{\_predictiveChartConfig} as the
\texttt{config} attribute's value:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
The area contained within the light-blue rectangle is the point from
which the predicted/forecasted values are shown:
\begin{figure}
\centering
\includegraphics{./images/chart-taglib-predictive.png}
\caption{A predictive chart lets you visualize estimated future data
alongside existing data.}
\end{figure}
Awesome! Now you know how to create predictive charts for your apps.
\section{Related Topics}\label{related-topics-42}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/Line-charts}{Line Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/combination-charts}{Combination
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/geomap-charts}{Geomap
Charts}
\end{itemize}
\chapter{Refreshing Charts to Reflect Real Time
Data}\label{refreshing-charts-to-reflect-real-time-data}
The polling interval property is an optional property for all charts. It
specifies the time in milliseconds for the chart's data to refresh. You
can use this for charts that receive any kind of real time data, such as
a JSON file that changes periodically. This ensures that the chart is up
to date, reflecting the most recent data. Follow these steps to
configure your chart to use real time data.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add a new java scriptlet and create a new instance of the chart's
object, and put the data into the \texttt{data} attribute. Finally,
set the chart's polling interval with the
\texttt{setPollingInterval()} method. An example \texttt{view.jsp}
configuration is shown below:
\begin{verbatim}
```java
<%
LineChartConfig _pollingIntervalLineChartConfig = new LineChartConfig();
_pollingIntervalLineChartConfig.put("data", "/foo.json");
_pollingIntervalLineChartConfig.setPollingInterval(2000);
%>
```
\end{verbatim}
\item
Set the chart taglib's \texttt{config} attribute to the updated
configuration object that you created in the last step, as shown in
the example below:
\begin{verbatim}
```markup
```
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/chart-polling-interval.png}
\caption{The polling interval property lets you refresh charts at a
given interval to reflect real time data.}
\end{figure}
Now you know how to reflect real time data in your charts!
\section{Related Topics}\label{related-topics-43}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/bar-charts}{Bar Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/scatter-charts}{Scatter
Charts}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/donut-charts}{Donut
Charts}
\end{itemize}
\chapter{Using AUI Taglibs}\label{using-aui-taglibs}
The AUI tag library provides tags that implement commonly used UI
components. These tags make your markup consistent, responsive, and
accessible.
You can find a list of the available
\texttt{\textless{}aui\textgreater{}} taglibs in the
\href{https://docs.liferay.com/portal/7.2-latest/taglibs/util-taglib/aui/tld-summary.html}{AUI
taglibdocs}. Each taglib has a list of attributes that can be passed to
the tag. Some of these are required, and some are optional. See the
taglibdocs to view the requirements for each tag. You'll find the full
markup generated by the tags in their JSPs in their
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/portal-web/docroot/html/taglib/aui}{Liferay
Github Repo} folders.
To use the AUI taglib library in your apps, you must add the following
declaration to your JSP:
\begin{verbatim}
<%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %>
\end{verbatim}
The AUI taglib is also available via a macro for your FreeMarker theme
templates and web content templates. Follow this syntax:
\begin{verbatim}
<@liferay_aui["tag-name"] attribute="string value" attribute=10 />
\end{verbatim}
This section covers how to create UI components with the AUI taglibs.
Each article contains code examples along with a screenshot of the
resulting UI.
\chapter{Building Forms with AUI
Tags}\label{building-forms-with-aui-tags}
The
\href{https://docs.liferay.com/portal/7.2-latest/taglibs/util-taglib/aui/tld-summary.html}{AUI
tag library} provides all the components you need to build forms for
your applications. AUI tags provide many benefits to standard form
elements, such as custom namespacing, localization, and even validation.
They provide multiple attributes that let you create the experience you
want for your users.
Follow these steps to build a form using AUI tags:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the \texttt{aui} taglib declaration to your portlet's
\texttt{view.jsp} if you haven't already:
\begin{verbatim}
<%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %>
\end{verbatim}
\item
Build your form using the tags shown below. Each tag links to the
corresponding taglibdoc that list the available attributes:
\begin{itemize}
\tightlist
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/input.html}{\texttt{\textless{}aui:input\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/button.html}{\texttt{\textless{}aui:button\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/button-row.html}{\texttt{\textless{}aui:button-row\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/container.html}{\texttt{\textless{}aui:container\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/col.html}{\texttt{\textless{}aui:col\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/row.html}{\texttt{\textless{}aui:row\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/field-wrapper.html}{\texttt{\textless{}aui:field-wrapper\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/fieldset.html}{\texttt{\textless{}aui:fieldset\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/fieldset-group.html}{\texttt{\textless{}aui:fieldset-group\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/form.html}{\texttt{\textless{}aui:form\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/select.html}{\texttt{\textless{}aui:select\textgreater{}}}
\item
\href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/option.html}{\texttt{\textless{}aui:option\textgreater{}}}
\end{itemize}
An example form is shown below:
\begin{verbatim}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/aui-taglib-basic-form.png}
\caption{The AUI tags provide everything you need to build forms for
your applications.}
\end{figure}
\item
Optionally add validation to your form fields. Nest a
\texttt{\textless{}aui:validator\textgreater{}} tag inside each form
field that you want to validate. Specify the validation rule with the
\texttt{\textless{}aui:validator\textgreater{}} tag's \texttt{name}
attribute (The available validation rules are shown in the table
below). You can override a field's default validation error message
with the \texttt{errorMessage} attribute. An example configuration is
shown below:
\begin{verbatim}
'#password'
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/aui-taglib-form-validation.png}
\caption{The AUI tags also provide validation for form fields.}
\end{figure}
The full list of available validation rules is shown in the table
below:
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
Rule | Description | Default Error Message |
--- | --- | --- |
`acceptFiles` | Specifies that the field can only contain the file types given. Each file extension must be separated by a comma. For example `'jpg,png,tif,gif' ` | 'Please enter a file with a valid extension ([supported extensions]).' |
`alpha` | Permits alphabetic characters | 'Please enter only alpha characters.' |
`alphanum` | Permits alphanumeric characters | 'Please enter only alphanumeric characters.' |
`date` | Permits dates | 'Please enter a valid date.' |
`digits` | Permits digits | 'Please enter only digits.' |
`email` | Permits an email address | 'Please enter a valid email address.' |
`equalTo` | Permits contents equal to another field with the specified field ID. For example, `'# password' ` | 'Please enter the same value again.' |
`max` | Permits an integer value less than the specified value. For example, a max value of 20 is specified with `20 ` | 'Please enter a value less than or equal to [max value].' |
`maxLength` | Permits a maximum field length of the specified size (follows the same syntax as `max`) | 'Please enter no more than [max] characters.' |
`min` | Permits an integer value greater than the specified minimum value (follows the same syntax as `max`) | 'Please enter a value greater than or equal to [min value].' |
`minLength` | Permits a field length longer than the specified size (follows the same syntax as `max`). | 'Please enter at least [min] characters.' |
`number` | Permits numerical values | 'Please enter a valid number.' |
`range` | Permits a number between the specified range. For example, a range between 1.23 and 10 is specified here `[1.23,10] ` | 'Please enter a value between [0] and [1].' |
`rangeLength` | Permits a field length between the specified range (follows the same syntax as `range`) | 'Please enter a value between [0] and [1] characters long.' |
`required` | Prevents a blank field | 'This field is required.' |
`url` | Permits a URL value | 'Please enter a valid URL.' |
\end{verbatim}
\noindent\hrulefill
Now you know how to build user-friendly forms for your applications.
\section{Related Topics}\label{related-topics-44}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-chart-taglib-in-your-portlets}{Using
the Chart Taglib in Your Portlets}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-liferay-front-end-taglibs-in-your-portlet}{Using
Liferay Front-end Taglibs in Your Portlet}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-clay-taglib-in-your-portlets}{Using
the Clay Taglib in Your portlets}
\end{itemize}
\chapter{liferay-npm-bundler}\label{liferay-npm-bundler}
The liferay-npm-bundler is a bundler (like
\href{https://webpack.github.io/}{Webpack} or
\href{http://browserify.org/}{Browserify} ) that targets Liferay DXP as
a platform and assumes you're using your npm packages from widgets (as
opposed to typical web applications).
The workflow for running npm packages inside widgets is slightly
different from standard bundlers. Instead of bundling the JavaScript in
a single file, you must \emph{link} all packages together in the browser
when the full web page is assembled. This lets widgets share common
versions of modules instead of each one loading its own copy. The
liferay-npm-bundler handles this for you.
\noindent\hrulefill
\textbf{Note:} You can also find information for the liferay-npm-bundler
in the project's
\href{https://github.com/liferay/liferay-npm-build-tools/wiki}{Wiki}.
\noindent\hrulefill
\section{How the Liferay npm Bundler Works
Internally}\label{how-the-liferay-npm-bundler-works-internally}
The liferay-npm-bundler takes a widget project and outputs its files
(including npm packages) to a build folder, so the standard widget build
(Gradle) can produce an OSGi bundle. You can learn more about the build
folder's structure in
\href{/docs/7-2/reference/-/knowledge_base/r/the-structure-of-osgi-bundles-containing-npm-packages}{The
Structure of OSGi Bundles Containing NPM Packages} reference.
The liferay-npm-bundler uses the process below to create the OSGi
bundle:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Copy the project's \texttt{package.json} file to the output directory.
\item
Traverse the project's dependency tree to determine its dependencies.
\item
For the project,
\begin{enumerate}
\def\labelenumii{\alph{enumii}.}
\item
Run the source files, specified in the \texttt{.npmbundlerrc}
configuration, through the rules.
\item
Pre-process the project's package with any configured plugins.
\item
Run \href{https://babeljs.io/}{Babel} with configured plugins for
each \texttt{.js} file inside the project.
\item
Post-process the project package with any configured plugins.
\end{enumerate}
\item
For each npm package dependency:
\begin{enumerate}
\def\labelenumii{\alph{enumii}.}
\item
Copy the npm package to the output folder and prefix the bundle's
name to it. Note that the bundler stores packages in a plain
\emph{bundle-name\$package}@\emph{version} format, rather than the
standard node\_modules tree format. To determine what is copied, the
bundler invokes a plugin to filter the package file list.
\item
Run rules on the package files.
\item
Pre-process the npm package with any configured plugins.
\item
Run \href{https://babeljs.io/}{Babel} with configured plugins for
each \texttt{.js} file inside the npm package.
\item
Post-process the npm package with any configured plugins.
\end{enumerate}
\end{enumerate}
The only difference between the pre-process and post-process steps are
when they are run (before or after Babel is run, respectively). During
this workflow, liferay-npm-bundler calls all the configured plugins so
they can perform transformations on the npm packages (for instance,
modifying their \texttt{package.json} files, or deleting or moving
files).
\noindent\hrulefill
\textbf{Note:} that the pre, post, and Babel phases were designed for
the old mode of operation (See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/migrating-your-project-to-use-the-new-mode}{Migrating
Your Project to Use the New Mode} for more information) and they will
gradually be replaced with rules for the new mode.
\noindent\hrulefill
In this reference section, you'll learn more about the
liferay-npm-bundler's configuration, default presets, format, and more.
\chapter{\texorpdfstring{Understanding the \texttt{.npmbundlerrc}'s
Structure}{Understanding the .npmbundlerrc's Structure}}\label{understanding-the-.npmbundlerrcs-structure}
The liferay-npm-bundler is configured via a \texttt{.npmbundlerrc} file
placed in the widget project's root folder. You can create a complete
configuration manually or extend a configuration preset (via Babel).
This article explains the \texttt{.npmbundlerrc} file's structure. See
the
\href{/docs/7-2/reference/-/knowledge_base/r/how-the-default-preset-configures-the-liferay-npm-bundler}{default
preset reference} to learn how the default preset configures the
liferay-npm-bundler. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-and-bundling-javascript-widgets-with-javascript-tooling}{Creating
JavaScript Widgets with JavaScript Tooling} to learn how to use the
liferay-npm-bundler along with the Liferay JS Generator to create
JavaScript widgets.
\section{The Structure}\label{the-structure}
The \texttt{.npmbundlerrc} file has four possible phase definitions:
\emph{copy-process}, \emph{pre-process}, \emph{post-process}, and
\emph{babel}. These phase definitions are explained in more detail
below:
\textbf{Copy-Process:} Defined with the \texttt{copy-plugins} property
(only available for dependency packages). Specifies which files should
be copied or excluded from each given package.
\textbf{Pre-Process:} Defined with the \texttt{plugins} property.
Specifies plugins to run before the Babel phase is run.
\textbf{Babel:} Defined with the \texttt{.babelrc} definition. Specifies
the \texttt{.babelrc} file to use when running Babel through the
package's \texttt{.js} files.
\noindent\hrulefill
\textbf{Note:} During this phase, Babel transforms package files (for
example, to convert them to AMD format, if necessary), but doesn't
transpile them. In theory, you could also transpile them by configuring
the proper plugins. We recommend transpiling before running the bundler,
to avoid mixing both unrelated processes.
\noindent\hrulefill
\textbf{Post-Process:} Defined with the \texttt{post-plugins} property.
An alternative to using the \emph{pre-process} phase, this specifies
plugins to run after the Babel phase has completed.
Here's an example of a \texttt{.npmbundlerrc} configuration:
\begin{verbatim}
{
"exclude": {
"*": [
"test/**/*"
],
"some-package-name": [
"test/**/*",
"bin/**/*"
],
"another-package-name@1.0.10": [
"test/**/*",
"bin/**/*",
"lib/extras-1.0.10.js"
]
},
"include-dependencies": [
"isobject", "isarray"
],
"output": "build",
"verbose": false,
"dump-report": true,
"config": {
"imports": {
"npm-angular5-provider": {
"@angular/common": "^5.0.0",
"@angular/core": "^5.0.0"
}
}
},
"/": {
"plugins": ["resolve-linked-dependencies"],
".babelrc": {
"presets": ["liferay-standard"]
},
"post-plugins": [
"namespace-packages",
"inject-imports-dependencies"
]
},
"*": {
"copy-plugins": ["exclude-imports"],
"plugins": ["replace-browser-modules"],
".babelrc": {
"presets": ["liferay-standard"]
},
"post-plugins": [
"namespace-packages",
"inject-imports-dependencies",
"inject-peer-dependencies"
]
},
"packages": {
"a-package-name": [
"copy-plugins": ["exclude-imports"],
"plugins": ["replace-browser-modules"],
".babelrc": {
"presets": ["liferay-standard"]
},
"post-plugins": [
"namespace-packages",
"inject-imports-dependencies",
"inject-peer-dependencies"
]
],
"other-package-name@1.0.10": [
"copy-plugins": ["exclude-imports"],
"plugins": ["replace-browser-modules"],
".babelrc": {
"presets": ["liferay-standard"]
},
"post-plugins": [
"namespace-packages",
"inject-imports-dependencies",
"inject-peer-dependencies"
]
]
}
}
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} Not all definition formats (\texttt{*},
\texttt{some-package-name}, and \texttt{some-package-name@version})
shown above are required. In most cases, the wildcard definition
(\texttt{*}) is enough. The non-wildcard formats
(\texttt{some-package-name} and \texttt{some-package-name@version}) are
rare exceptions for packages that require a more specific configuration
than the wildcard definition provides.
\noindent\hrulefill
\section{Standard Configuration
Options}\label{standard-configuration-options}
Below are the standard configuration options for the
\texttt{.npmbundlerrc} file:
\texttt{config}: Defines the global configuration that is made available
to all liferay-npm-bundler and Babel plugins. Please refer to each
plugin's documentation to find the available options for each specific
plugin.
\begin{verbatim}
{
"config": {
"imports": {
"vuejs-provider": {
"vue": "^2.0.0"
}
}
}
}
\end{verbatim}
\texttt{dump-report:} Sets whether to generate a debugging report. If
\texttt{true}, a \texttt{liferay-npm-bundler-report.html} file is
generated in the project directory that describes all actions and
decisions taken when processing project and npm modules. Note that you
can also pass this as the build flag
\texttt{\$\ liferay-npm-bundler\ -\/-dump-report} or
\texttt{\$\ liferay-npm-bundler\ -r}. The default value is
\texttt{false}.
\texttt{no-tracking:} whether to send usage analytics to our servers.
Note that you can also pass this as a build flag with the CLI argument
\texttt{\$\ liferay-npm-bundler\ -\/-no-tracking}, or by creating a
marker file called \texttt{.liferay-npm-bundler-no-tracking} in the
project's root folder or any of its ancestors, or by setting the
environment variable
\texttt{LIFERAY\_NPM\_BUNDLER\_NO\_TRACKING=\textquotesingle{}\textquotesingle{}}.
The default value is \texttt{false}.
\texttt{output:} by default the bundler writes packages to the standard
Gradle resources folder:
\texttt{build/resources/main/META-INF/resources}. Set this value to
override the default output folder. Note that the dependency npm
packages are placed in a \texttt{node\_modules} folder inside the build
folder. Note if \texttt{create-jar} is set, the default output folder is
\texttt{build}.
\texttt{preset:} specifies the \texttt{liferay-npm-bundler} preset to
use as a base configuration. Note that if a \texttt{.npmbundlerrc} file
is not provided, the default
\texttt{liferay-npm-bundler-preset-standard} preset is used. All
settings provided by the preset are inherited, but they can be
overridden.
\texttt{verbose:} Sets whether to output detailed information about what
the tool is doing to the console. The default value is \texttt{false}.
\section{Package Processing Options}\label{package-processing-options}
\texttt{"/"}: plugins' configuration for the project's package.
\texttt{"\textbackslash{}"}: plugins' configuration for dependency
packages.
\emph{(asterisk)}: Defines the default plugin configuration for all npm
packages. It contains four values identified by a corresponding key.
Keys \texttt{copy-plugins}, \texttt{plugins} and \texttt{post-plugins}
identify arrays of \texttt{liferay-npm-bundler} plugins to apply in the
copy, pre and post process steps. Key \texttt{.babelrc} identifies an
object specifying the configuration to use in the Babel step and has the
same structure of a standard \texttt{.babelrc} file.
\texttt{exclude:} defines glob expressions of files to exclude from
bundling from all or specific packages. Each list is an array identified
by one of the following keys: \texttt{*} (any package),
\texttt{\{package\ name\}} (any version of the package), or
\texttt{\{package\ name\}@\{version\}} (a specific version of a
package). Below is an example configuration:
\begin{verbatim}
{
"exclude": {
"*": ["__tests__/**/*"],
"is-object": ["test/**/*"],
"is-array@1.0.1": ["test/**/*", "Makefile"]
}
}
\end{verbatim}
\texttt{ignore:} skips processing the specified JavaScript files with
Babel for the project. An example configuration is shown below:
\begin{verbatim}
{
"ignore": ["lib/legacy/**/*.js"]
}
\end{verbatim}
\texttt{include-dependencies:} defines packages to include in bundling,
even if they are not listed under the \texttt{dependencies} section of
\texttt{package.json}. These packages must be available in the
\texttt{node\_modules} folder (i.e.~installed manually, without saving
them to \texttt{package.json}, or listed in the \texttt{devDependencies}
section).
\texttt{packages:} defines plugin configuration for npm packages, per
package.
\texttt{max-parallel-files:} Defines the maximum number of files to
process in parallel to avoid EMFILE errors (especially on Windows). The
default value is \texttt{128}.
\texttt{process-serially:} \textbf{Note}: removed since v 2.7.0.
Replaced with \texttt{max-parallel-files}.
\texttt{rules:} defines rules to apply to the projects source files with
the loader. Rules must have a \texttt{use} array property that defines
the loader to use, which can be specified using a package name or an
object with \texttt{loader} and \texttt{options} properties if
applicable, and one or more of the properties below:
\begin{itemize}
\tightlist
\item
\texttt{test}: defines a regular expression to filter files in the
\texttt{sources} folders to determine whether to apply rules to them.
The project-relative path of each eligible file is compared against
the regular expression and files that match are processed by the
loaders.
\item
\texttt{exclude}: refines the \texttt{test} expression by specifying
files to exclude.
\item
\texttt{include}: refines the \texttt{test} expression by specifying
files to include.
\end{itemize}
Here's an example configuration:
\begin{verbatim}
{
"rules": [
{
"test": "\\.js$",
"exclude": "node_modules",
"use": [
{
"loader": "babel-loader",
"options": {
"presets": ["env", "react"]
}
}
]
},
{
"test": "\\.css$",
"use": ["style-loader"]
},
{
"test": "\\.json$",
"use": ["json-loader"]
}
]
}
\end{verbatim}
\texttt{sources:} rules apply to files in these project folders. Folders
can be nested (e.g.~\texttt{/src/main/resources/}) and must be written
using POSIX path separators (i.e.~use \texttt{/} instead of
\texttt{\textbackslash{}} on Win32 systems). Note that rules are
automatically applied to package dependency files of the project.
An example configuration is shown below:
\begin{verbatim}
{
"sources": ["src", "assets"]
}
\end{verbatim}
\section{OSGi Bundle Creation
Options}\label{osgi-bundle-creation-options}
Since version 2.2.0, the liferay-npm-bundler can create widget OSGi
bundles for you. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-and-bundling-javascript-widgets-with-javascript-tooling}{Creating
and Bundling JavaScript Widgets with JavaScript Tooling} for complete
instructions. The configuration options for OSGi bundle creation are
shown below:
\begin{itemize}
\tightlist
\item
\textbf{create-jar}: Creates an OSGi bundle when set to a truthy
value. When set to \texttt{true}, all sub-options take default values.
When an object is passed, each sub-option can be configured
individually. Note that you can also pass this as a build flag:
\texttt{\$\ liferay-npm-bundler\ -\/-create-} or
\texttt{\$\ liferay-npm-bundler\ -j}. The default value is
\texttt{false}.
\end{itemize}
\begin{verbatim}
{
"create-jar": true
}
\end{verbatim}
\begin{itemize}
\item
\textbf{create-jar.auto-deploy-portlet}: \textbf{Note} that this
option is deprecated. Use the \texttt{create-jar.features.js-extender}
option instead.
\item
\textbf{create-jar.features.configuration}: specifies the file
describing the system (OSGi) and widget instance (widget preferences,
as defined in the Portlet spec) configuration to use. (see
\href{/docs/7-2/frameworks/-/knowledge_base/f/configuring-system-settings-and-instance-settings-for-your-js-widget}{Configuring
System Settings and Instance Settings for Your JavaScript Widgets} for
more information on the required settings configuration). The default
value is \texttt{features/configuration.json} if that file exists,
otherwise the default is \texttt{undefined}.
\end{itemize}
\begin{verbatim}
{
"create-jar": {
"features": {
"configuration": "features/configuration.json"
}
}
}
\end{verbatim}
\begin{itemize}
\tightlist
\item
\textbf{create-jar.output-dir:} specifies where to place the final JAR
\end{itemize}
\begin{verbatim}
{
"create-jar": {
"output-dir": "dist"
}
}
\end{verbatim}
\begin{itemize}
\tightlist
\item
\textbf{create-jar.features.js-extender:} controls whether to process
the OSGi bundle with the JS Portlet Extender. You can also specify the
minimum required version of the Extender to use for the bundle. This
can be useful if you want to use advanced features in your bundle, but
you want it to be deployable in older versions of the Extender. Pass
the string \texttt{"any"} to let the bundle deploy in any version of
the Extender. If \texttt{true}, the liferay-npm-bundler automatically
determines the minimum version of the Extender required for the
features used in the bundle. the default value is \texttt{true}. An
example configuration is shown below:
\end{itemize}
\begin{verbatim}
{
"create-jar": {
"features": {
"js-extender": "1.1.0"
}
}
}
\end{verbatim}
\begin{itemize}
\tightlist
\item
\textbf{create-jar.features.web-context:} specifies the context path
to use for publishing bundle's static resources. The default value is
\texttt{/\{project\ name\}-\{project\ version\}}.
\end{itemize}
\begin{verbatim}
{
"create-jar": {
"features": {
"web-context": "/my-project"
}
}
}
\end{verbatim}
\begin{itemize}
\tightlist
\item
\textbf{create-jar.features.localization:} specifies the L10N file to
use for the bundle (see
\href{/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-widget}{Providing
Localization in Your JavaScript Widgets} for more information on using
localization in your widget. The default value is
\texttt{features/localization/Language} if a properties file with that
base name exists, otherwise the default is \texttt{undefined}.
\end{itemize}
\begin{verbatim}
{
"create-jar": {
"features": {
"localization": "features/localization/Language"
}
}
}
\end{verbatim}
\begin{itemize}
\tightlist
\item
\textbf{create-jar.features.settings:} \textbf{Note} that this option
is deprecated. Use the \texttt{create-jar.features.configuration}
option instead.
\end{itemize}
\noindent\hrulefill
\textbf{Note:} Plugins' configuration specifies the options for
configuring plugins in all the possible phases, as well as the
\texttt{.babelrc} file to use when running Babel (see
\href{https://babeljs.io/docs/usage/babelrc/}{Babel's documentation} for
more information on that file format).
\noindent\hrulefill
\noindent\hrulefill
\textbf{Note:} Prior to version 1.4.0 of the liferay-npm-bundler,
package configurations were placed next to the tools options
(\texttt{*}, \texttt{output}, \texttt{exclude}, etc.) To prevent package
name collisions, package configurations are now namespaced and placed
under the \texttt{packages} section. To maintain backwards
compatibility, the liferay-npm-bundler falls back to the root section
outside \texttt{packages} for package configuration, if no package
configurations (\texttt{package-name@version}, \texttt{package-name}, or
\texttt{*}) are found in the \texttt{packages} section.
\noindent\hrulefill
Now you know the structure of the \texttt{.npmbundlerrc} file!
\chapter{How the Default Preset Configures the
liferay-npm-bundler}\label{how-the-default-preset-configures-the-liferay-npm-bundler}
The liferay-npm-bundler comes with a default configuration preset:
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-preset-standard}{\texttt{liferay-npm-bundler-preset-standard}}
in your \texttt{.npmbundlerrc} file. This preset configures several
plugins for the build process and is automatically used (even if the
\texttt{.npmbundlerrc} is missing), unless you override it with one of
your own. Running the liferay-npm-bundler with this preset applies the
\href{https://github.com/liferay/liferay-npm-build-tools/blob/master/packages/liferay-npm-bundler-preset-standard/config.json}{config
file} from \texttt{liferay-npm-bundler-preset-standard}:
\begin{verbatim}
{
"/": {
"plugins": ["resolve-linked-dependencies"],
".babelrc": {
"presets": ["liferay-standard"]
},
"post-plugins": ["namespace-packages", "inject-imports-dependencies"]
},
"*": {
"copy-plugins": ["exclude-imports"],
"plugins": ["replace-browser-modules"],
".babelrc": {
"presets": ["liferay-standard"]
},
"post-plugins": [
"namespace-packages",
"inject-imports-dependencies",
"inject-peer-dependencies"
]
}
}
\end{verbatim}
The configuration above states that for all npm packages (\texttt{*})
the pre-process phase (\texttt{plugins}) must run the
\texttt{replace-browser-modules} plugin. Setting this to
\texttt{post-plugins} would run it during the post phase instead.
\noindent\hrulefill
\textbf{Note:} You can override configuration preset values by adding
your own configuration to your project's \texttt{.npmbundlerrc} file.
For instance, using the configuration preset example above, you can
define your own \texttt{.babelrc} value in \texttt{.npmbundlerrc} file
to override the defined ``liferay-standard'' babelrc preset.
\noindent\hrulefill
The
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/babel-preset-liferay-standard}{\texttt{liferay-standard}
preset} applies the following plugins to packages:
\begin{itemize}
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-plugin-exclude-imports}{exclude-imports}:
Exclude packages declared in the \texttt{imports} section from the
build.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-plugin-inject-imports-dependencies}{inject-imports-dependencies}:
Inject dependencies declared in the \texttt{imports} section in the
dependencies' \texttt{package.json} files.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-plugin-inject-peer-dependencies}{inject-peer-dependencies}:
Inject declared peer dependencies (as they are resolved in the
project's \texttt{node\_modules} folder) in the dependencies'
\texttt{package.json} files.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-plugin-namespace-packages}{namespace-packages}:
Namespace package names based on the root project's package name to
isolate packages per project and avoid collisions. This prepends
\texttt{\textless{}project-package-name\textgreater{}\$} to each
package name appearance in \texttt{package.json} files.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-plugin-replace-browser-modules}{replace-browser-modules}:
Replaces the server side files for modules listed under
\texttt{browser}/\texttt{unpkg}/\texttt{jsdelivr} section of
\texttt{package.json} with their browser counterparts.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-plugin-resolve-linked-dependencies}{resolve-linked-dependencies}:
Replace linked dependencies versions appearing in
\texttt{package.json} files (those obtained from local file system or
GitHub, for example) by their real version number, as resolved in the
project's \texttt{node\_modules} directory.
\end{itemize}
In addition, the bundler runs Babel with the
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/babel-preset-liferay-standard}{babel-preset-liferay-standard}
preset, that invokes the following plugins:
\begin{itemize}
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/babel-plugin-normalize-requires}{babel-plugin-normalize-requires}:
Normalize AMD \texttt{require()} calls.
\item
\href{https://github.com/babel/minify/tree/master/packages/babel-plugin-transform-node-env-inline}{babel-plugin-transform-node-env-inline}:
Inline the \texttt{NODE\_ENV} environment variable, and if it's part
of a binary expression (eg.
\texttt{process.env.NODE\_ENV\ ===\ "development"}), then statically
evaluate and replace it.
\item
\href{https://www.npmjs.com/package/babel-plugin-minify-dead-code-elimination}{babel-plugin-minify-dead-code-elimination}:
Inline bindings when possible. Tries to evaluate expressions and
prunes unreachable as a result.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/babel-plugin-wrap-modules-amd}{babel-plugin-wrap-modules-amd}:
Wrap modules inside an AMD \texttt{define()} module.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/babel-plugin-name-amd-modules}{babel-plugin-name-amd-modules}:
Name AMD modules based on package name, version, and module path.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/babel-plugin-namespace-modules}{babel-plugin-namespace-modules}:
Namespace modules based on the root project's package name, prepending
\texttt{\textless{}project-package-name\textgreater{}\$}. Wrap modules
inside an AMD \texttt{define()} module for each module name appearance
(in \texttt{define()} or \texttt{require()} calls) so that the
packages are localized per project and don't clash.
\item
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/babel-plugin-namespace-amd-define}{babel-plugin-namespace-amd-define}:
Add a prefix to AMD \texttt{define()} calls (by default
\texttt{Liferay.Loader.}).
\end{itemize}
Now you know the available configuration presets for
\texttt{.npmbundlerrc} and how they work.
\chapter{The Structure of OSGi Bundles Containing npm
Packages}\label{the-structure-of-osgi-bundles-containing-npm-packages}
To deploy JavaScript modules, you must create an OSGi bundle with the
npm dependencies extracted from the project's \texttt{node\_modules}
folder and modify them to work with the
\href{https://github.com/liferay/liferay-amd-loader}{Liferay AMD
Loader}. The liferay-npm-bundler automates this process for you,
creating a bundle similar to the one below:
\begin{itemize}
\tightlist
\item
\texttt{my-bundle/}
\begin{itemize}
\tightlist
\item
\texttt{META-INF/}
\begin{itemize}
\tightlist
\item
\texttt{resources/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package
\item
version: 1.0.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
my-bundle-package\$isarray: 2.0.0
\item
my-bundle-package\$isobject: 2.1.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{lib/}
\begin{itemize}
\tightlist
\item
\texttt{index.js}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\item
\texttt{node\_modules/}
\begin{itemize}
\tightlist
\item
\texttt{my-bundle-package\$isobject@2.1.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isobject
\item
version: 2.1.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
my-bundle-package\$isarray: 1.0.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{my-bundle-package\$isarray@1.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isarray
\item
version: 1.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{my-bundle-package\$isarray@2.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isarray
\item
version: 2.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
The packages inside \texttt{node\_modules} are the same format as the
npm tool and can be copied (after a little processing for things like
converting to AMD, for example) from a standard \texttt{node\_modules}
folder. The \texttt{node\_modules} folder can hold any number of npm
packages (even different versions of the same package), or no npm
packages at all.
Now that you know the structure for OSGi bundles containing npm
packages, you can learn how the liferay-npm-bundler handles inline
JavaScript packages.
\section{Inline JavaScript packages}\label{inline-javascript-packages}
The resulting OSGi bundle that the liferay-npm-bundler creates lets you
deploy one inline JavaScript package (named \texttt{my-bundle-package}
in the example) with several npm packages that are placed inside the
\texttt{node\_modules} folder, one package per folder.
The inline package is nested in the OSGi standard
\texttt{META-INF/resources} folder and is defined by a standard npm
\texttt{package.json} file.
The inline package is optional, but only one inline package is allowed
per OSGi bundle. The inline package usually provides the JavaScript code
for a widget, when the OSGi bundle contains one. Note that the
architecture does not differentiate between inline and npm packages once
they are published. The inline package is only used for organizational
purposes.
Now you know how the liferay-npm-bundler creates OSGi bundles for npm
packages!
\chapter{How the Liferay npm Bundler Publishes npm
Packages}\label{how-the-liferay-npm-bundler-publishes-npm-packages}
When you deploy an OSGi bundle with the specified structure, as
explained in
\href{/docs/7-2/reference/-/knowledge_base/r/the-structure-of-osgi-bundles-containing-npm-packages}{The
Structure of OSGi Bundles Containing NPM Packages} reference, its
modules are made available for consumption through canonical URLs. To
better illustrate resolved modules, the example structure below is the
standard structure that the liferay-npm-bundler 1.x generates, and
therefore doesn't have the namespaced packages that the 2.x version
generates. Please refer to the last sections of this article to know how
liferay-npm-bundler 2.0 overrides this de-duplication mechanism to
implement isolated dependencies and imports.
\begin{itemize}
\tightlist
\item
\texttt{my-bundle/}
\begin{itemize}
\tightlist
\item
\texttt{META-INF/}
\begin{itemize}
\tightlist
\item
\texttt{resources/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package
\item
version: 1.0.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
isarray: 2.0.0
\item
isobject: 2.1.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{lib/}
\begin{itemize}
\tightlist
\item
\texttt{index.js}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\item
\texttt{node\_modules/}
\begin{itemize}
\tightlist
\item
\texttt{isobject@2.1.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: isobject
\item
version: 2.1.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
isarray: 1.0.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{isarray@1.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: isarray
\item
version: 1.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{isarray@2.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: isarray
\item
version: 2.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
If you deploy the example OSGi bundle shown above, the following URLs
are made available (one for each module):
\begin{itemize}
\item
\url{http://localhost/o/js/module/598/my-bundle-package@1.0.0/lib/index.js}
\item
\url{http://localhost/o/js/module/598/isobject@2.1.0/index.js}
\item
\url{http://localhost/o/js/module/598/isarray@1.0.0/index.js}
\item
\url{http://localhost/o/js/module/598/isarray@2.0.0/index.js}
\end{itemize}
\noindent\hrulefill
\textbf{NOTE:} The OSGi bundle ID (598) may vary.
\noindent\hrulefill
You can learn about package de-duplication next.
\section{Package De-duplication}\label{package-de-duplication}
Since two or more OSGi modules may export multiple copies of the same
package and version, Liferay Portal must de-duplicate such collisions.
To accomplish de-duplication, a new concept called \emph{resolved
module} was created.
A resolved module is the reference package exported to Liferay Portal's
front-end, when multiple copies of the same package and version exist.
It's randomly referenced from one of the several bundles exporting the
same copies of the package.
Using the example from the previous section, for each group of canonical
URLs referring to the same module inside different OSGi bundles, there's
another canonical URL for the resolved module. The example structure has
the resolved module URLs shown below:
\begin{itemize}
\item
\url{http://localhost/o/js/resolved-module/my-bundle-package@1.0.0/lib/index.js}
\item
{[}http://localhost/o/js/resolved-module/my-bundle-package\(isobject@2.1.0/index.js](http://localhost/o/js/resolved-module/my-bundle-package\)isobject@2.1.0/index.js)
\item
{[}http://localhost/o/js/resolved-module/my-bundle-package\(isarray@1.0.0/index.js](http://localhost/o/js/resolved-module/my-bundle-package\)isarray@1.0.0/index.js)
\item
{[}http://localhost/o/js/resolved-module/my-bundle-package\(isarray@2.0.0/index.js](http://localhost/o/js/resolved-module/my-bundle-package\)isarray@2.0.0/index.js)
\end{itemize}
\noindent\hrulefill
\textbf{NOTE:} The OSGi bundle ID (598 in the example) is removed and
module is replaced by \texttt{resolved-module}.
\noindent\hrulefill
Next you can learn how the bundler (since version 2.0.0) isolates
package dependencies. See
\href{/docs/7-2/reference/-/knowledge_base/r/what-changed-between-liferay-npm-bundler-1-x-and-2-x}{What
Changed Between liferay-npm-bundler 1.x and 2.x} for more information on
why this change was made.
\section{Isolated Package
Dependencies}\label{isolated-package-dependencies}
A typical OSGi bundle structure generated with liferay-npm-bundler 2.x
is shown below:
\begin{itemize}
\tightlist
\item
\texttt{my-bundle/}
\begin{itemize}
\tightlist
\item
\texttt{META-INF/}
\begin{itemize}
\tightlist
\item
\texttt{resources/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package
\item
version: 1.0.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
my-bundle-package\$isarray: 2.0.0
\item
my-bundle-package\$isobject: 2.1.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{lib/}
\begin{itemize}
\tightlist
\item
\texttt{index.js}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\item
\texttt{node\_modules/}
\begin{itemize}
\tightlist
\item
\texttt{my-bundle-package\$isobject@2.1.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isobject
\item
version: 2.1.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
my-bundle-package\$isarray: 1.0.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{my-bundle-package\$isarray@1.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isarray
\item
version: 1.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{my-bundle-package\$isarray@2.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isarray
\item
version: 2.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
Note that each package dependency is namespaced with the bundle's name
(\texttt{my-bundle-package\$} in the example structure). This lets each
project load its own dependencies and avoid potential collisions with
projects that export the same package. For example, consider the two
widget projects below:
\begin{itemize}
\tightlist
\item
\texttt{my-widget}
\begin{itemize}
\tightlist
\item
package.json
\begin{itemize}
\tightlist
\item
dependencies:
\begin{itemize}
\tightlist
\item
a-library 1.0.0
\item
a-helper 1.0.0
\end{itemize}
\end{itemize}
\item
node\_modules
\begin{itemize}
\tightlist
\item
a-library
\begin{itemize}
\tightlist
\item
version: 1.0.0
\item
dependencies:
\begin{itemize}
\tightlist
\item
a-helper \^{}1.0.0
\end{itemize}
\end{itemize}
\item
a-helper
\begin{itemize}
\tightlist
\item
version: 1.0.0
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{another-widget}
\begin{itemize}
\tightlist
\item
package.json
\begin{itemize}
\tightlist
\item
dependencies:
\begin{itemize}
\tightlist
\item
a-library 1.0.0
\item
a-helper 1.2.0
\end{itemize}
\end{itemize}
\item
node\_modules
\begin{itemize}
\tightlist
\item
a-library
\begin{itemize}
\tightlist
\item
version: 1.0.0
\item
dependencies:
\begin{itemize}
\tightlist
\item
a-helper \^{}1.0.0
\end{itemize}
\end{itemize}
\item
a-helper
\begin{itemize}
\tightlist
\item
version: 1.2.0
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
In this example, \texttt{a-library} depends on \texttt{a-helper} at
version 1.0.0 or higher (note the caret \^{} expression in the
dependencies). The bundler implements isolated dependencies by prefixing
the name of the bundle to the modules, so that \texttt{my-widget} gets
its \texttt{a-helper} at 1.0.0, while \texttt{another-widget} gets its
\texttt{a-helper} at 1.2.0.
The dependencies isolation not only avoids collisions between bundles,
but also makes peer dependencies behave deterministically as each widget
gets what it had in its \texttt{node\_modules} folder when it was
developed.
Now that you understand how namespacing modules isolates bundle
dependencies, avoiding collisions, you can learn about de-duplication
next.
\section{De-duplication through
Importing}\label{de-duplication-through-importing}
Isolated dependencies are very useful, but there are times when sharing
the same package between modules would be more beneficial. To do this,
the liferay-npm-bundler lets you import packages from an external OSGi
bundle, instead of using your own. This lets you put shared dependencies
in one project and reference them from the rest.
Imagine that you have three widgets that compose the homepage of your
site: \texttt{my-toolbar}, \texttt{my-menu}, and \texttt{my-content}.
These widgets depend on the fake, but awesome, Wonderful UI Components
(WUI) framework. This quite limited framework is composed of only three
packages:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
\texttt{component-core}
\item
\texttt{button}
\item
\texttt{textfield}
\end{enumerate}
Since the bundler namespaces each dependency package with the widget's
name by default, you would end up with three namespaced copies of the
WUI package on the page. This is not what you want. Since they share the
same package, instead you can create a fourth bundle that contains the
WUI package, and import the WUI package in the three widgets. This
results in the structure below:
\begin{itemize}
\tightlist
\item
\texttt{my-toolbar/}
\begin{itemize}
\tightlist
\item
\texttt{.npmbundlerrc}
\begin{itemize}
\tightlist
\item
config:
\begin{itemize}
\tightlist
\item
imports:
\begin{itemize}
\tightlist
\item
wui-provider:
\begin{itemize}
\tightlist
\item
component-core: \^{}1.0.0
\item
button: \^{}1.0.0
\item
textfield: \^{}1.0.0
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{my-menu/}
\begin{itemize}
\tightlist
\item
\texttt{.npmbundlerrc}
\begin{itemize}
\tightlist
\item
config:
\begin{itemize}
\tightlist
\item
imports:
\begin{itemize}
\tightlist
\item
wui-provider:
\begin{itemize}
\tightlist
\item
component-core: \^{}1.0.0
\item
button: \^{}1.0.0
\item
textfield: \^{}1.0.0
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{my-content/}
\begin{itemize}
\tightlist
\item
\texttt{.npmbundlerrc}
\begin{itemize}
\tightlist
\item
config:
\begin{itemize}
\tightlist
\item
imports:
\begin{itemize}
\tightlist
\item
wui-provider:
\begin{itemize}
\tightlist
\item
component-core: \^{}1.0.0
\item
button: \^{}1.0.0
\item
textfield: \^{}1.0.0
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{wui-provider/}
\begin{itemize}
\tightlist
\item
\texttt{.package.json}
\begin{itemize}
\tightlist
\item
name: wui-provider
\item
dependencies:
\begin{itemize}
\tightlist
\item
component-core: 1.0.0
\item
button: 1.0.0
\item
textfield: 1.0.0
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
The bundler switches the namespace of certain packages, thus pointing
them to an external bundle. Say that you have the following code in
\texttt{my-toolbar} widget:
\begin{verbatim}
var Button = require('button');
\end{verbatim}
By default, the bundler 2.x transforms this into the following when not
imported from another bundle:
\begin{verbatim}
var Button = require('my-toolbar$button');
\end{verbatim}
But, because \texttt{button} is imported from \texttt{wui-provider}, it
is instead changed to the value below:
\begin{verbatim}
var Button = require('wui-provider$button');
\end{verbatim}
Also, a dependency on \texttt{wui-provider\$button} at version
\texttt{\^{}1.0.0} is included in \texttt{my-toolbar}'s
\texttt{package.json} file so that the loader finds the correct version.
That's all you need. Once \texttt{wui-provider\$button} is required at
runtime, it jumps to \texttt{wui-provider}'s context and loads the
subdependencies from there on, even if code is executed from
\texttt{my-toolbar}. This works because, as you can imagine,
\texttt{wui-provider}'s modules are namespaced too, and once you load a
module from it, it keeps requiring \texttt{wui-provider\$} prefixed
modules all the way down.
Next, you will learn possible strategies for importing.
\section{Strategies When Importing
Packages}\label{strategies-when-importing-packages}
De-duplication by importing is a powerful tool, but you must design a
versioning strategy suitable for you so that you don't run into errors.
First of all, you must decide if you want to declare imported
dependencies only in the \texttt{.npmbundlerrc} file or in the
\texttt{package.json} too. Listing an imported dependency in
\texttt{.npmbundlerrc} is enough, even if it isn't present in your
\texttt{node\_modules} folder because during runtime the loader will
find it. Listing an imported dependency in \texttt{.npmbundlerrc} is
enough, even if it isn't present in your \texttt{node\_modules} folder,
because during runtime the loader finds it. If you have previous
experience with dynamic linking support in standard operating systems,
think of it as a DLL or shared object.
You may need to install your dependencies in \texttt{node\_modules} too
if you use them for tests, or if they contain types needed to compile
(like in Typescript), etc. If that is the case, then you can place them
in the \texttt{dependencies} or \texttt{devDependencies} section of your
\texttt{package.json}. If you list them under the latter, they are
automatically excluded from the output bundle by the
liferay-npm-bundler. Otherwise, you need to exclude them in the
\texttt{.npmbundlerrc} file so they don't redundantly appear in the
output.
If you list dependencies both in \texttt{package.json} and
\texttt{.npmbundlerrc}, decide how to keep versions in sync. The best
advice is to use the same version constraints in both files, but you may
decide not to do so if it is necessary. For example, imagine that you
import one of your dependencies from another bundle during runtime to
run tests. Say you are using version constraint \^{}1.5.1. It would be
desirable that if you have tested your code with a version
\textgreater=1.5.1 and \textless2.0.0 (that's what \^{}1.5.1 means), you
get a compatible version during runtime. Thus, you would declare the
dependency with \^{}1.5.1 in \texttt{.npmbundlerrc} too.
However, there are times when you may want to be more lenient, and you
may need to get a lower version (1.4.0 for example) during runtime, even
if you are developing against \^{}1.5.1. In that case, you can declare
\^{}1.5.1 in your \texttt{package.json} and \^{}1.0.0 in
\texttt{.npmbundlerrc}.
In the end, it's up to you to decide how you want to handle your
dependencies:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\texttt{package.json} (While developing)
\item
\texttt{.npmbundlerrc} (During runtime)
\end{enumerate}
we recommend that you choose a versioning strategy and stick to it, to
ensure dependencies are satisfied at runtime.
\chapter{Understanding How liferay-npm-bundler Formats JavaScript
Modules for
AMD}\label{understanding-how-liferay-npm-bundler-formats-javascript-modules-for-amd}
Liferay AMD Loader is based on the
\href{https://github.com/amdjs/amdjs-api/wiki/AMD}{AMD specification}.
All modules inside an npm OSGi bundle must be in AMD format. This is
done for \href{http://www.commonjs.org/}{CommonJS} modules by wrapping
the module code inside a \texttt{define} call. The liferay-npm-bundler
helps automate this process by wrapping the module for you. This article
references the OSGi structure below as an example. You can learn more
about this structure in
\href{/docs/7-2/reference/-/knowledge_base/r/the-structure-of-osgi-bundles-containing-npm-packages}{The
Structure of OSGi Bundles Containing NPM Packages} reference.
\begin{itemize}
\tightlist
\item
\texttt{my-bundle/}
\begin{itemize}
\tightlist
\item
\texttt{META-INF/}
\begin{itemize}
\tightlist
\item
\texttt{resources/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package
\item
version: 1.0.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
my-bundle-package\$isarray: 2.0.0
\item
my-bundle-package\$isobject: 2.1.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{lib/}
\begin{itemize}
\tightlist
\item
\texttt{index.js}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\item
\texttt{node\_modules/}
\begin{itemize}
\tightlist
\item
\texttt{my-bundle-package\$isobject@2.1.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isobject
\item
version: 2.1.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
my-bundle-package\$isarray: 1.0.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{my-bundle-package\$isarray@1.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isarray
\item
version: 1.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{my-bundle-package\$isarray@2.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package\$isarray
\item
version: 2.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
For example, the \texttt{my-bundle-package\$isobject@2.1.0} package's
\texttt{index.js} file contains the following code:
\begin{verbatim}
'use strict';
var isArray = require('my-bundle-package$isarray');
module.exports = function isObject(val) {
return val != null && typeof val === 'object' && isArray(val) === false;
};
\end{verbatim}
The updated module code configured for AMD format is shown below:
\begin{verbatim}
define(
'my-bundle-package$isobject@2.1.0/index',
['module', 'require', 'my-bundle-package$isarray'],
function (module, require) {
'use strict';
var define = undefined;
var isArray = require('my-bundle-package$isarray');
module.exports = function isObject(val) {
return val != null && typeof val === 'object'
&& isArray(val) === false;
};
}
);
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} The module's name must be based on its package, version,
and file path (for example
\texttt{my-bundle-package\$isobject@2.1.0/index}), otherwise Liferay AMD
Loader can't find it.
\noindent\hrulefill
Note the module's dependencies:
\texttt{{[}\textquotesingle{}module\textquotesingle{},\ \textquotesingle{}require\textquotesingle{},\ \textquotesingle{}my-bundle-package\$isarray\textquotesingle{}{]}}.
\texttt{module} and \texttt{require} must be used to get a reference to
the \texttt{module.exports} object and the local \texttt{require}
function, as defined in the AMD specification.
The subsequent dependencies state the modules on which this module
depends. Note that \texttt{my-bundle-package\$isarray} in the example is
not a package but rather an alias of the
\texttt{my-bundle-package\$isarray} package's main module (thus, it is
equivalent to \texttt{my-bundle-package\$isarray/index}).
Also note that there is enough information in the \texttt{package.json}
files to know that \texttt{my-bundle-package\$isarray} refers to
\texttt{my-bundle-package\$isarray/index}, but also that it must be
resolved to version \texttt{1.0.0} of such package, i.e., that
\texttt{my-bundle-package\$isarray/index} in this case refers to
\texttt{my-bundle-package\$isarray@1.0.0/index}.
You may also have noted the \texttt{var\ define\ =\ undefined;} addition
to the top of the file. This is introduced by
\texttt{liferay-npm-bundler} to make the module think that it is inside
a CommonJS environment (instead of an AMD one). This is because some npm
packages are written in UMD format and, because we are wrapping it
inside our AMD \texttt{define()} call, we don't want them to execute
their own \texttt{define()} but prefer them to take the CommonJS path,
where the exports are done through the \texttt{module.exports} global.
Now you have a better understanding of how liferay-npm-bundler formats
JavaScript modules for AMD!
\chapter{Understanding How Liferay AMD Loader Configuration is
Exported}\label{understanding-how-liferay-amd-loader-configuration-is-exported}
\noindent\hrulefill
\textbf{NOTE:} This article is for users who know how Liferay AMD Loader
works under the hood. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/loading-amd-modules-in-liferay}{Liferay
AMD Module Loader} for more information.
\noindent\hrulefill
With
\href{/docs/7-2/reference/-/knowledge_base/r/how-the-liferay-npm-bundler-publishes-npm-packages\#package-de-duplication}{de-duplication}
in place, JavaScript modules are made available to Liferay AMD Loader
through the configuration returned by the
\texttt{/o/js\_loader\_modules} URL.
The OSGi bundle shown below is used for reference in this article:
\begin{itemize}
\tightlist
\item
\texttt{my-bundle/}
\begin{itemize}
\tightlist
\item
\texttt{META-INF/}
\begin{itemize}
\tightlist
\item
\texttt{resources/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: my-bundle-package
\item
version: 1.0.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
isarray: 2.0.0
\item
isobject: 2.1.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{lib/}
\begin{itemize}
\tightlist
\item
\texttt{index.js}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\item
\texttt{node\_modules/}
\begin{itemize}
\tightlist
\item
\texttt{isobject@2.1.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: isobject
\item
version: 2.1.0
\item
main: lib/index
\item
dependencies:
\begin{itemize}
\tightlist
\item
isarray: 1.0.0
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{isarray@1.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: isarray
\item
version: 1.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\item
\texttt{isarray@2.0.0/}
\begin{itemize}
\tightlist
\item
\texttt{package.json}
\begin{itemize}
\tightlist
\item
name: isarray
\item
version: 2.0.0
\item
\ldots{}
\end{itemize}
\item
\ldots{}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
For example, for the specified structure (shown above), as explained in
\href{/docs/7-2/reference/-/knowledge_base/r/the-structure-of-osgi-bundles-containing-npm-packages}{The
Structure of OSGi Bundles Containing npm Packages} reference, the
following configuration is published for Liferay AMD loader to consume:
\begin{verbatim}
Liferay.PATHS = {
...
'my-bundle-package@1.0.0/lib/index': '/o/js/resolved-module/my-bundle-package@1.0.0/lib/index',
'isobject@2.1.0/index': '/o/js/resolved-module/isobject@2.1.0/index',
'isarray@1.0.0/index': '/o/js/resolved-module/isarray@1.0.0/index',
'isarray@2.0.0/index': '/o/js/resolved-module/isarray@2.0.0/index',
...
}
Liferay.MODULES = {
...
"my-bundle-package@1.0.0/lib/index.es": {
"dependencies": ["exports", "isarray", "isobject"],
"map": {
"isarray": "isarray@2.0.0",
"isobject": "isobject@2.1.0"
}
},
"isobject@2.1.0/index": {
"dependencies": ["module", "require", "isarray"],
"map": {
"isarray": "isarray@1.0.0"
}
},
"isarray@1.0.0/index": {
"dependencies": ["module", "require"],
"map": {}
},
"isarray@2.0.0/index": {
"dependencies": ["module", "require"],
"map": {}
},
...
}
Liferay.MAPS = {
...
'my-bundle-package@1.0.0': { value: 'my-bundle-package@1.0.0/lib/index', exactMatch: true}
'isobject@2.1.0': { value: 'isobject@2.1.0/index', exactMatch: true},
'isarray@2.0.0': { value: 'isarray@2.0.0/index', exactMatch: true},
'isarray@1.0.0': { value: 'isarray@1.0.0/index', exactMatch: true},
...
}
\end{verbatim}
Note:
\begin{itemize}
\item
The \texttt{Liferay.PATHS} property describes paths to the JavaScript
module files.
\item
The \texttt{Liferay.MODULES} property describes the dependency names
and versions of each module.
\item
The \texttt{Liferay.MAPS} property describes the aliases of the
package's main modules.
\end{itemize}
\chapter{What Changed Between Liferay npm Bundler 1.x and
2.x}\label{what-changed-between-liferay-npm-bundler-1.x-and-2.x}
This reference doc outlines the key changes between liferay-npm-bundler
version 1.x and 2.x.
\section{Automatically Formatting Modules for
AMD}\label{automatically-formatting-modules-for-amd}
In version series 1.x of the bundler it was the developer's
responsibility to wrap project modules in an AMD \texttt{define()} call.
However, since 2.x the bundler does it for you, so the only requisite is
that the project's code is transpiled/written for CommonJS modules model
(the standard model for module handling in Node.js, that uses
\texttt{require()} calls to load modules).
\section{Isolating Project
Dependencies}\label{isolating-project-dependencies}
Package names are prefixed with the bundle name since version 2.0.0 of
the bundler, but were left intact in previous versions. This strategy is
used to isolate packages from different bundles. You can still deploy
bundler 1.x packages (without prefix), and they will still work as they
did for previous versions of the bundler.
\section{Improved Peer Dependency
Support}\label{improved-peer-dependency-support}
In bundler 1.x, there was only one shared peer dependency package
available between widgets. With isolated dependencies per widget, it's
easy to honor peer dependencies perfectly. Peer dependencies can be
resolved exactly as stated in projects because their names are prefixed
with the project's name. This is possible because of the new
\href{https://github.com/liferay/liferay-npm-build-tools/tree/master/packages/liferay-npm-bundler-plugin-inject-peer-dependencies}{liferay-npm-bundler-plugin-inject-peer-dependencies}
plugin. It scans all JS modules for \texttt{require} calls. If the
bundler finds a required package in the \texttt{main.js} file, but it is
not declared in the \texttt{package.json}, it resolves it to the proper
version that is found in the \texttt{node\_modules} folder. The plugin
then injects a new dependency in the output \texttt{package.json} for
the required package.
Note that injected dependency version constraints are the specific
version number required, without caret or any other semantic version
operator. This is to honor the exact peer dependency found in the
project. Injecting more relaxed semantic version expressions could lead
to unstable results.
\section{Manually De-duplicating Through
Importing}\label{manually-de-duplicating-through-importing}
Namespacing means that each widget gets its own dependencies. Only using
the bundler this way obtains the same functionality as standard bundlers
like webpack or Browserify, so you wouldn't need a specific tool like
liferay-npm-bundler. Since Liferay DXP is a widget based architecture,
sharing dependencies among different widgets would be very beneficial.
In bundler 1.x that deduplication was made automatically, but there was
no control over it. However, with version 2.x, you may now import
packages from an external OSGi bundle, instead of using your own. This
lets you put shared dependencies in one project, and reference them from
the rest. Though This new way of de-duplication is not automatic, it
leads to full control (during build time) of how each package is
resolved.
Now that you understand what changed between version 1.x and 2.x of the
liferay-npm-bundler, you can follow the steps in the
\href{/docs/7-2/frameworks/-/knowledge_base/f/migrating-a-liferay-npm-bundler-project-from-1-x-to-2-x}{Migrating
a liferay-npm-bundler Project from 1.x to 2.x} to migrate your 1.x
projects to 2.x.
\chapter{Understanding liferay-npm-bundler's
Loaders}\label{understanding-liferay-npm-bundlers-loaders}
liferay-npm-bundler's mechanism is inspired by webpack. Like webpack,
the liferay-npm-bundler processes files using a set of rules that
include loaders that transform a project's source files before producing
the final output.
\noindent\hrulefill
\textbf{Note:} While webpack creates a single JS bundle file,
liferay-npm-bundler targets an AMD loader, so webpack and
liferay-npm-bundler loaders are not compatible.
\noindent\hrulefill
Loaders are npm packages that export a function in their main module
that receives source files and returns modified files, and optionally
new files, based on the loader's configuration. For example, the
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-babel-loader}{babel-loader}
receives ES6+ JavaScript files, runs Babel on them, and returns
transpiled ES5 files along with a generated source map. You can use this
pattern to
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-loaders-for-the-liferay-npm-bundler}{create
custom loaders}. A few example loader functions are shown below:
\begin{itemize}
\tightlist
\item
Pass JavaScript files through Babel or TSC
\item
Convert CSS files into JS modules that dynamically inject the CSS into
the HTML page
\item
Process CSS files with SASS
\item
Create tools that generate code based on
\href{https://en.wikipedia.org/wiki/Interface_description_language}{IDL}
files
\end{itemize}
Loaders are configured via the project's \texttt{.npmbundlerrc} file. A
loader's configuration is specified using two key options:
\texttt{sources} (the folders that contain the sources files to process)
and \texttt{rules} (the loaders, options---if applicable---and regular
expressions that determine which files to process). See
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-the-npmbundlerrcs-structure\#package-processing-options}{Understanding
the \texttt{.npmbundlerrc}'s Structure} for more information on the
configuration requirements and options.
Loaders can be chained. Files are processed by the loaders in the order
they are listed in the \texttt{use} property. The files are passed to
the first loader, processed, sent to the next loader, and so on, until
the files are processed by the rules. You can run complex processes,
such as converting a SASS file into CSS with the sass-loader, and then
convert it into a JavaScript module with the style-loader. Once the
rules are applied, the liferay-npm-bundler continues with the pre, post,
and babel phases of the bundler plugins.
\chapter{Default liferay-npm-bundler
Loaders}\label{default-liferay-npm-bundler-loaders}
Several
\href{/docs/7-2/frameworks/-/knowledge_base/f/understanding-liferay-npm-bundlers-loaders}{loaders}
are available for the liferay-npm-bundler by default:
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-babel-loader}{\texttt{babel-loader}}:
processes source files with \href{https://babeljs.io/}{Babel}. This
avoids an extra build step before the bundler.
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-copy-loader}{\texttt{copy-loader}}:
copies source files (static assets) to the output folder.
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-css-loader}{\texttt{css-loader}}:
converts a CSS file into a JavaScript module that's inserted into the
DOM once it's loaded.
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-json-loader}{\texttt{json-loader}}:
generates JavaScript modules that export the contents of a JSON file as
an object, so you can include JSON files with the \texttt{require()}
call.
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-sass-loader}{\texttt{sass-loader}}:
runs \texttt{node-sass} or \texttt{sass} on source files. This lets you
generate static CSS files. It can be chained before
\texttt{style-loader}.
\href{https://github.com/liferay/liferay-js-toolkit/tree/master/packages/liferay-npm-bundler-loader-style-loader}{\texttt{style-loader}}:
converts a CSS file into a JavaScript module that inserts the CSS
contents into the DOM once it's loaded. This lets you include CSS files
with a \texttt{require()} call.
See the
\href{https://github.com/izaera/liferay-js-toolkit-showcase/tree/loaders}{liferay-js-toolkit
loaders showcase} for an example use case of the liferay-npm-bundler's
loaders. If the default loaders don't meet your requirements, you can
follow the instructions in
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-loaders-for-the-liferay-npm-bundler}{Creating
Custom Loaders for the Bundler} to create your own loaders.
\chapter{Liferay JavaScript APIs}\label{liferay-javascript-apis}
The \texttt{Liferay} JavaScript object exposes methods, objects, and
properties that you can use to access Liferay DXP-specific information.
This section contains a comprehensive list of some of the most useful
utilities you can find inside the \texttt{Liferay} object.
\chapter{Accessing ThemeDisplay
Information}\label{accessing-themedisplay-information}
The \texttt{Liferay} global JavaScript Object exposes useful methods,
objects, and properties, each containing a wealth of information, one of
which is \texttt{ThemeDisplay}. If you have experience with Java
development in Liferay DXP, you may be familiar with ThemeDisplay. The
JavaScript object exposes the same information as the ThemeDisplay Java
Class. It gives you access to valuable information that you can use in
your applications, such as the Portal instance, the current user, the
user's language, whether the user is signed in or being impersonated,
the file path to the theme's resources, and much more.
The \texttt{Liferay} global object is automatically available in Liferay
DXP at runtime. To access the \texttt{ThemeDisplay} object, use the
following dot notation in your app:
\begin{verbatim}
Liferay.ThemeDisplay.method-name
\end{verbatim}
This reference describes some of the most commonly used
\texttt{ThemeDisplay} methods for retrieving IDs, file paths, and login
information. An exhaustive list of all of the available methods is
displayed in the table below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Method
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Type
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
getLayoutId & number & \\
getLayoutRelativeURL & string & Returns the relative URL for the page \\
getLayoutURL & string & \\
getParentLayoutId & number & \\
isControlPanel & boolean & \\
isPrivateLayout & boolean & \\
isVirtualLayout & boolean & \\
getBCP47LanguageId & number & \\
getCDNBaseURL & string & Returns the content delivery network (CDN) base
URL, or the current portal URL if the CDN base URL is null \\
getCDNDynamicResourcesHost & string & Returns the content delivery
network (CDN) dynamic resources host, or the current portal URL if the
CDN dynamic resources host is null \\
getCDNHost & string & \\
getCompanyGroupId & number & \\
getCompanyId & number & Returns the portal instance ID \\
getDefaultLanguageId & number & \\
getDoAsUserIdEncoded & string & \\
getLanguageId & number & Returns the user's language ID \\
getParentGroupId & number & \\
getPathContext & string & \\
getPathImage & string & Returns the relative path of the portlet's image
directory \\
getPathJavaScript & string & Returns the relative path of the directory
containing the portlet's JavaScript source files \\
getPathMain & string & Returns the path of the portal instance's main
directory \\
getPathThemeImages & string & Returns the path of the current theme's
image directory \\
getPathThemeRoot & string & Returns the relative path of the current
theme's root directory \\
getPlid & string & Returns the primary key of the page \\
getPortalURL & string & Returns the portal instance's base URL \\
getScopeGroupId & number & Returns the ID of the scoped or sub-scoped
active group (e.g.~site) \\
getScopeGroupIdOrLiveGroupId & number & \\
getSessionId & number & Returns the session ID, or a blank string if the
session ID is not available to the application \\
getSiteGroupId & number & \\
getURLControlPanel & string & \\
getURLHome & string & \\
getUserId & number & Returns the ID of the user for which the current
request is being handled \\
getUserName & string & Returns the user's name \\
isAddSessionIdToURL & boolean & \\
isFreeformLayout & boolean & \\
isImpersonated & boolean & Returns \texttt{true} if the current user is
being impersonated. Authorized administrative users can
\href{/docs/7-2/user/-/knowledge_base/u/adding-editing-and-deleting-users\#editing-users}{impersonate}
act as another user to test that user's account \\
isSignedIn & boolean & Returns \texttt{true} if the user is logged in to
the portal \\
isStateExclusive & boolean & \\
isStateMaximized & boolean & \\
isStatePopUp & boolean & \\
\end{longtable}
\noindent\hrulefill
The example configuration below alerts users with a standard message if
they are a guest or a personal greeting if they are signed in. This is a
basic example, and perhaps a bit invasive, but it illustrates how you
can create unique experiences for each user with the
\texttt{ThemeDisplay} APIs:
\begin{verbatim}
if(Liferay.ThemeDisplay.isSignedIn()){
alert('Hello ' + Liferay.ThemeDisplay.getUserName() + '. Welcome Back.')
}
else {
alert('Hello Guest.')
}
\end{verbatim}
\chapter{Working with URLs in
JavaScript}\label{working-with-urls-in-javascript}
The \texttt{Liferay} global JavaScript Object exposes methods, objects,
and properties that access the portal context. Four of these are helpful
when working with URLS: \texttt{authToken}, \texttt{currentURL},
\texttt{currentURLEncoded}, and \texttt{PortletURL}. If you have
experience with Java development in Liferay DXP, you may have worked
with some of these before. The \texttt{Liferay} global object is
automatically available at runtime, so no additional dependencies are
required.
\noindent\hrulefill
\textbf{Note:} Since Liferay DXP SP1 and Liferay Portal CE 7.2 GA2, the
\texttt{Liferay.PortletURL} utilities are deprecated and have been
replaced with \texttt{Liferay.Util.PortletURL} utilities. We recommend
that you use the updated versions to ensure future compatibility. The
examples below use the updated utilities.
\noindent\hrulefill
This covers how to use the \texttt{Liferay} global JavaScript object to
manipulate URLs. A list of the available methods and properties appears
in the tables shown below. Example configurations are shown below the
tables.
\section{Portlet URL Methods}\label{portlet-url-methods}
\texttt{Liferay.Util.PortletURL} Methods:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Method
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Parameters
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Returns
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{createPortletURL} & \texttt{basePortletURL}, \texttt{parameters}
& A portlet URL as a \href{https://url.spec.whatwg.org/\#api}{URL}
object \\
\texttt{createActionURL} & \texttt{basePortletURL}, \texttt{parameters}
& A portlet URL as a \href{https://url.spec.whatwg.org/\#api}{URL}
object \\
\texttt{createRenderURL} & \texttt{basePortletURL}, \texttt{parameters}
& A portlet URL as a \href{https://url.spec.whatwg.org/\#api}{URL}
object \\
\texttt{createResourceURL} & \texttt{basePortletURL},
\texttt{parameters} & A portlet URL as a
\href{https://url.spec.whatwg.org/\#api}{URL} object \\
\end{longtable}
\noindent\hrulefill
\section{Liferay Util PortletURL}\label{liferay-util-portleturl}
\texttt{Liferay.Util.PortletURL} provides APIs for creating portlet URLs
(\texttt{actionURL}, \texttt{renderURL}, and \texttt{resourceURL}) with
JavaScript in your JSPs. Below is an example configuration for a JSP:
\begin{verbatim}
var basePortletURL = 'https://localhost:8080/group/control_panel/manage?p_p_id=com_liferay_roles_admin_web_portlet_RolesAdminPortlet';
var actionURL = Liferay.Util.PortletURL.createActionURL(
basePortletURL,
{
'javax.portlet.action': 'addUser',
foo: 'bar'
}
);
console.log(actionURL.toString());
// https://localhost:8080/group/control_panel/manage?p_p_id=com_liferay_roles_admin_web_portlet_RolesAdminPortlet&javax.portlet.action=addUser&com_liferay_roles_admin_web_portlet_RolesAdminPortlet_foo=bar&p_p_lifecycle=1
\end{verbatim}
The same API is available as a module for use in your JavaScript files.
The ES6 example below uses the \texttt{createActionURL} module:
\begin{verbatim}
import {createActionURL} from 'frontend-js-web';
var basePortletURL = 'https://localhost:8080/group/control_panel/manage?p_p_id=com_liferay_roles_admin_web_portlet_RolesAdminPortlet';
var actionURL = createActionURL(
basePortletURL,
{
'p_p_id': Liferay.PortletKeys.DOCUMENT_LIBRARY,
foo: 'bar'
}
);
\end{verbatim}
See the \hyperref[portlet-url-methods]{Portlet URL Methods} section for
more information about the method used in the example above.
\section{Liferay AuthToken}\label{liferay-authtoken}
The \texttt{Liferay.authToken} property holds the current authentication
token value as a String. The \texttt{authToken} is used to validate
permissions when you make calls to services. To use the
\texttt{authToken} in a URL, pass \texttt{Liferay.authToken} as the
URL's \texttt{p\_auth} parameter, as shown in the example below:
\begin{verbatim}
import {createActionURL} from 'frontend-js-web';
var basePortletURL = 'https://localhost:8080/group/control_panel/manage?p_p_id=com_liferay_roles_admin_web_portlet_RolesAdminPortlet';
var actionURL = createActionURL(
basePortletURL,
{
'p_auth': Liferay.authToken
}
);
\end{verbatim}
\section{Liferay CurrentURL}\label{liferay-currenturl}
The \texttt{Liferay.currentURL} property holds the path of the current
URL from the server root.
For example, if checked from \texttt{my.domain.com/es/web/guest/home},
the value is \texttt{/es/web/guest/home}, as shown below:
\begin{verbatim}
// Inside my.domain.com/es/web/guest/home
console.log(Liferay.currentURL); // "/es/web/guest/home"
\end{verbatim}
\section{Liferay CurrentURLEncoded}\label{liferay-currenturlencoded}
The \texttt{Liferay.currentURLEncoded} property holds the path of the
current URL, encoded in ASCII for safe transmission over the Internet,
from the server root.
For example, if checked from \texttt{my.domain.com/es/web/guest/home},
the value is \texttt{\%2Fes\%2Fweb\%2Fguest\%2Fhome}, as shown below:
\begin{verbatim}
// Inside my.domain.com/es/web/guest/home
console.log(Liferay.currentURLEncoded); // "%2Fes%2Fweb%2Fguest%2Fhome"
\end{verbatim}
Now you know how to manipulate URLs using methods within the
\texttt{Liferay} global JavaScript object.
\chapter{Liferay DXP JavaScript
Utilities}\label{liferay-dxp-javascript-utilities}
This reference explains some of the utility methods and objects inside
the \texttt{Liferay} global JavaScript object.
\section{Retrieve Browser
Information}\label{retrieve-browser-information}
The \texttt{Liferay.Browser} object contains methods that expose the
current user agent characteristics without the need of accessing and
parsing the global \texttt{window.navigator} object.
The available methods for the \texttt{Liferay.Browser} object are listed
in the table below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Method
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Return Type
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
acceptsGzip & boolean & Returns whether the browser accepts gzip file
compression \\
getMajorVersion & number & Returns the major version of the browser \\
getRevision & number & Returns the revision version of the browser \\
getVersion & number & Returns the major.minor version of the browser \\
isAir & boolean & Returns whether the browser is Adobe AIR \\
isChrome & boolean & Returns whether the browser is Chrome \\
isFirefox & boolean & Returns whether the browser is Firefox \\
isGecko & boolean & Returns whether the browser is Gecko \\
isIe & boolean & Returns whether the browser is Internet Explorer \\
isIphone & boolean & Returns whether the browser is on an Iphone \\
isLinux & boolean & Returns whether the browser is being viewed on
Linux \\
isMac & boolean & Returns whether the browser is being viewed on Mac \\
isMobile & boolean & Returns whether the browser is being viewed on a
mobile device \\
isMozilla & boolean & Returns whether the browser is Mozilla \\
isOpera & boolean & Returns whether the browser is Opera \\
isRtf & boolean & Returns whether the browser supports RTF \\
isSafari & boolean & Returns whether the browser is Safari \\
isSun & boolean & Returns whether the browser is being viewed on Sun
OS \\
isWebKit & boolean & Returns whether the browser is WebKit \\
isWindows & boolean & Returns whether the browser is being viewed on
Windows \\
\end{longtable}
\noindent\hrulefill
Below is an example configuration:
\begin{verbatim}
Liferay.Browser.isChrome(); //returns true in Chrome
\end{verbatim}
\section{Format XML}\label{format-xml}
The \texttt{Liferay.Util.formatXML} utility takes XML content, as a
String, and returns it formatted.
Parameters: - \texttt{content}: The XML string to format -
\texttt{options}: An optional configuration object \texttt{\{\}} that
contains additional parameters for formatting the XML
The default configuration contains these options:
\begin{verbatim}
const DEFAULT_OPTIONS = {
newLine: NEW_LINE, //'\r\n'
tagIndent: TAG_INDENT //'\t'
};
\end{verbatim}
Below is an example configuration for a JSP that overwrites the default
options:
\begin{verbatim}
var options = {newLine: '\n', tagIndent: ' '};
var input = `
Foo
Bar FooBar
FooBarBaz!
`;
var formattedXMLString = Liferay.Util.formatXML(input, options);
console.log(formattedXMLString);
/*results:
\n'
\n'
\n'
Foo \n'
Bar \n'
FooBar \n'
FooBarBaz! \n'
';
*/
\end{verbatim}
\section{Format Storage Size}\label{format-storage-size}
The \texttt{Liferay.Util.formatStorage} utility takes a storage size
number (in bytes) and returns it in the proper format (KB, MB, or GB) as
a String.
Parameters: - \texttt{size}: The numerical value of the storage size in
bytes - \texttt{options}: An optional configuration object \texttt{\{\}}
that contains additional parameters for formatting the storage size
The default configuration contains these options:
\begin{verbatim}
const DEFAULT_OPTIONS = {
addSpaceBeforeSuffix: false,
decimalSeparator: '.',
denominator: 1024.0,
suffixGB: 'GB',
suffixKB: 'KB',
suffixMB: 'MB'
};
\end{verbatim}
Below is an example configuration that overwrites some of the default
options:
\begin{verbatim}
var formattedSize = Liferay.Util.formatStorage(1048576, {
addSpaceBeforeSuffix: true,
decimalSeparator: ',',
suffixMB: 'megabytes'
});
console.log(formattedSize); //1,0 megabytes
\end{verbatim}
\section{Store and Retrieve Session Form
data}\label{store-and-retrieve-session-form-data}
\texttt{Liferay.Util.setSessionValue()}: Sets a key, value pair for the
Store utility's fetch value.
Parameters: - \texttt{key}: The \texttt{formData} key (String) -
\texttt{value}: The \texttt{formData} key's corresponding value
(Object\textbar String)
\texttt{Liferay.Util.getSessionValue()}: Retrieves the Store utility's
fetch value for the given \texttt{key}.
Parameters: - \texttt{key}: The key (String) to fetch the value for.
Below is an example configuration for a JSP:
\begin{verbatim}
Liferay.Util.Session.set('state', 'open');
Liferay.Util.Session.get('state').then(function(value) {
console.log(value); //open
});
\end{verbatim}
Here is an example configuration that uses ES6:
\begin{verbatim}
import {getSessionValue, setSessionValue} from 'frontend-js-web';
setSessionValue('state', 'open');
getSessionValue('state').then(value =>{
console.log(value); //open
});
\end{verbatim}
\chapter{Invoking Liferay Services}\label{invoking-liferay-services}
Liferay DXP provides many web services out-of-the-box. To see a
comprehensive list of the available web services, navigate to
\texttt{http://localhost:8080/api/jsonws} (assuming your localhost is
running on port 8080).
This reference covers how to invoke these web services using JavaScript.
\section{Invoking Web Services via
JavaScript}\label{invoking-web-services-via-javascript}
7.0 contains a global JavaScript object called \texttt{Liferay} that has
many useful utilities. One method is \texttt{Liferay.Service}, which
invokes JSON web services.
The \texttt{Liferay.Service} method takes four possible arguments:
\textbf{service \{string\textbar object\}:} Specify the service name or
an object with the keys as the service to call, and the value as the
service configuration object. (Required)
\textbf{data \{object\textbar node\textbar string\}:} Specify the data
to send to the service. If the object passed is the ID of a form or a
form element, the form fields will be serialized and used as the data.
\textbf{successCallback \{function\}:} A function to execute when the
server returns a response. It receives a JSON object as its first
parameter.
\textbf{exceptionCallback \{function\}:} A function to execute when the
response from the server contains a service exception. It receives an
exception message as its first parameter.
One of the benefits of using the \texttt{Liferay.Service} method versus
using a standard AJAX request is that it handles the authentication for
you.
Below is an example configuration of the \texttt{Liferay.Service}
method:
\begin{verbatim}
Liferay.Service(
'/user/get-user-by-email-address',
{
companyId: Liferay.ThemeDisplay.getCompanyId(),
emailAddress: 'test@example.com'
},
function(obj) {
console.log(obj);
}
);
\end{verbatim}
The example above retrieves information about a user by passing its
\texttt{companyId} and \texttt{emailAddress}. The response data
resembles the following JSON object:
\begin{verbatim}
{
"agreedToTermsOfUse": true,
"comments": "",
"companyId": "20116",
"contactId": "20157",
"createDate": 1471990639779,
"defaultUser": false,
"emailAddress": "test@example.com",
"emailAddressVerified": true,
"facebookId": "0",
"failedLoginAttempts": 0,
"firstName": "Test",
"googleUserId": "",
"graceLoginCount": 0,
"greeting": "Welcome Test Test!",
"jobTitle": "",
"languageId": "en_US",
"lastFailedLoginDate": null,
"lastLoginDate": 1471996720765,
"lastLoginIP": "127.0.0.1",
"lastName": "Test",
"ldapServerId": "-1",
"lockout": false,
"lockoutDate": null,
"loginDate": 1472077523149,
"loginIP": "127.0.0.1",
"middleName": "",
"modifiedDate": 1472077523149,
"mvccVersion": "7",
"openId": "",
"portraitId": "0",
"reminderQueryAnswer": "test",
"reminderQueryQuestion": "what-is-your-father's-middle-name",
"screenName": "test",
"status": 0,
"timeZoneId": "UTC",
"userId": "20156",
"uuid": "c641a7c9-5acb-aa68-b3ea-5575e1845d2f"
}
\end{verbatim}
Now that you know how to send an individual request, you're ready to run
batch requests.
\section{Batching Requests}\label{batching-requests}
Another way to invoke the \texttt{Liferay.Service} method is by passing
an object with the keys of the service to call and the value of the
service configuration object.
Below is an example configuration for a batch request:
\begin{verbatim}
Liferay.Service(
{
'/user/get-user-by-email-address': {
companyId: Liferay.ThemeDisplay.getCompanyId(),
emailAddress: 'test@example.com'
}
},
function(obj) {
console.log(obj);
}
);
\end{verbatim}
You can invoke multiple services with the same request by passing in an
array of service objects. Here's an example:
\begin{verbatim}
Liferay.Service(
[
{
'/user/get-user-by-email-address': {
companyId: Liferay.ThemeDisplay.getCompanyId(),
emailAddress: 'test@example.com'
}
},
{
'/role/get-user-roles': {
userId: Liferay.ThemeDisplay.getUserId()
}
}
],
function(obj) {
// obj is now an array of response objects
// obj[0] == /user/get-user-by-email-address data
// obj[1] == /role/get-user-roles data
console.log(obj);
}
);
\end{verbatim}
Next you can learn how to nest your requests.
\section{Nesting Requests}\label{nesting-requests}
Nested service calls bind information from related objects together in a
JSON object. You can call other services in the same HTTP request and
conveniently nest returned objects.
You can use variables to reference objects returned from service calls.
Variable names must start with a dollar sign (\texttt{\$}).
The example in this section retrieves user data with
\texttt{/user/get-user-by-id} and uses the \texttt{contactId} returned
from that service to then invoke \texttt{/contact/get-contact} in the
same request.
\noindent\hrulefill
\textbf{Note:} You must flag parameters that take values from existing
variables. To flag a parameter, insert the \texttt{@} prefix before the
parameter name.
\noindent\hrulefill
Below is an example configuration that demonstrates these concepts:
\begin{verbatim}
Liferay.Service(
{
"$user = /user/get-user-by-id": {
"userId": Liferay.ThemeDisplay.getUserId(),
"$contact = /contact/get-contact": {
"@contactId": "$user.contactId"
}
}
},
function(obj) {
console.log(obj);
}
);
\end{verbatim}
Here is what the response data would look like for the request above:
\begin{verbatim}
{
"agreedToTermsOfUse": true,
"comments": "",
"companyId": "20116",
"contactId": "20157",
"createDate": 1471990639779,
"defaultUser": false,
"emailAddress": "test@example.com",
"emailAddressVerified": true,
"facebookId": "0",
"failedLoginAttempts": 0,
"firstName": "Test",
"googleUserId": "",
"graceLoginCount": 0,
"greeting": "Welcome Test Test!",
"jobTitle": "",
"languageId": "en_US",
"lastFailedLoginDate": null,
"lastLoginDate": 1472231639378,
"lastLoginIP": "127.0.0.1",
[...]
"screenName": "test",
"status": 0,
"timeZoneId": "UTC",
"userId": "20156",
"uuid": "c641a7c9-5acb-aa68-b3ea-5575e1845d2f",
"contact": {
"accountId": "20118",
"birthday": 0,
[...]
"createDate": 1471990639779,
"emailAddress": "test@example.com",
"employeeNumber": "",
"employeeStatusId": "",
"facebookSn": "",
"firstName": "Test",
"lastName": "Test",
"male": true,
"middleName": "",
"modifiedDate": 1471990639779,
[...]
"userName": ""
}
}
\end{verbatim}
Now that you know how to process requests, you can learn how to filter
the results.
\section{Filtering Results}\label{filtering-results}
If you don't want all the properties returned by a service, you can
define a whitelist of properties. This returns only the specific
properties you request in the object.
Below is an example of whitelisting properties:
\begin{verbatim}
Liferay.Service(
{
'$user[emailAddress,firstName] = /user/get-user-by-id': {
userId: Liferay.ThemeDisplay.getUserId()
}
},
function(obj) {
console.log(obj);
}
);
\end{verbatim}
To specify whitelist properties, place the properties in square brackets
(e.g., \texttt{{[}whiteList{]}}) immediately following the name of your
variable. The example above requests only the \texttt{emailAddress} and
\texttt{firstName} of the user.
Below is the filtered response:
\begin{verbatim}
{
"firstName": "Test",
"emailAddress": "test@example.com"
}
\end{verbatim}
Next you can learn how to populate the inner parameters of the request.
\section{Inner Parameters}\label{inner-parameters}
When you pass in an object parameter, you'll often need to populate its
inner parameters (i.e., fields).
Consider a default parameter \texttt{serviceContext} of type
\texttt{ServiceContext}. To make an appropriate call to JSON web
services you might need to set \texttt{serviceContext} fields such as
\texttt{scopeGroupId}, as shown below:
\begin{verbatim}
Liferay.Service(
'/example/some-web-service',
{
serviceContext: {
scopeGroupId: 123
}
},
function(obj) {
console.log(obj);
}
);
\end{verbatim}
\chapter{\texorpdfstring{Handling AJAX Requests with
\texttt{Liferay.Util.fetch}}{Handling AJAX Requests with Liferay.Util.fetch}}\label{handling-ajax-requests-with-liferay.util.fetch}
When you make Ajax requests (referred to as Service Resource
actions/requests in Liferay DXP), they must protect against
\href{https://en.wikipedia.org/wiki/Cross-site_request_forgery}{CSRF}
and include the proper credentials. Since Liferay DXP 7.2 SP1 and
Liferay CE Portal 7.2 GA2, Liferay DXP provides a
\texttt{Liferay.Util.fetch} utility based on the standard
\href{https://fetch.spec.whatwg.org/}{\texttt{fetch}} API that you can
use to make AJAX requests. It includes these key features:
\begin{itemize}
\tightlist
\item
A thin wrapper on ES6
\href{https://fetch.spec.whatwg.org/}{\texttt{fetch}} that shares the
same API
\item
Sets \texttt{credentials:include} to each request
\item
Sets \texttt{x-csrf-token} header to each request
\item
Requires no dependencies
\end{itemize}
Below is an example configuration in ES6:
\begin{verbatim}
import {fetch} from 'frontend-js-web';
fetch(url, {
body: new FormData(form),
method: 'POST'
})
.then(response => response.json())
.then(response => processData(response))
.then(response => failureCallback(error));
\end{verbatim}
Example use case in JSPs:
\begin{verbatim}
Liferay.Util.fetch(url, {
body: new FormData(form),
method: 'POST'
}).then(function(response) {
return response.json();
}).then(function(response) {
message.innerHTML = response.message;
}).catch(function() {
failureCallback();
});
\end{verbatim}
\noindent\hrulefill
\textbf{NOTE:} global access through \texttt{Liferay.Util} is only meant
for use in JSP code. In ES6, you must use the \texttt{fetch} module, as
shown in the JavaScript example above.
\chapter{Working with Addresses}\label{working-with-addresses}
The \texttt{Liferay} global JavaScript Object exposes methods, objects,
and properties that access the portal context. The
\texttt{Liferay.Address} utility contains methods for retrieving
information about the addresses country and region. The \texttt{Liferay}
global object is automatically available at runtime, so no additional
dependencies are required.
The available methods are listed below, along with an example
configuration.
\texttt{Liferay.Address.getCountries(callback)}: returns an Array of the
available countries.
Parameters: - \texttt{callback}: A callback function to post-process the
Array of countries
The example below prints the list of available regions for the selected
country (the United States in this case) in a table:
\begin{verbatim}
Liferay.Address.getCountries(function(e){console.table(e)}, 19);
\end{verbatim}
\texttt{Liferay.Address.getRegions(callback,\ selectKey)}: returns an
Array of the available regions, by country, for the specified region ID.
Parameters: - \texttt{callback}: A callback function to post-process the
Array of regions - \texttt{selectKey}: The selected region ID
The example below prints the list of available countries in a table to
the console:
\begin{verbatim}
Liferay.Address.getCountries(function(e){console.table(e)});
\end{verbatim}
This example uses an AUI \texttt{DynamicSelect} module to create a pair
of select fields in a JSP. The first field retrieves the countries with
the \texttt{Liferay.Address.getCountries()} method, and the second
select field is dynamically populated with the selected country's
available regions with the \texttt{Liferay.Address.getRegions()} method:
\begin{verbatim}
new Liferay.DynamicSelect(
[
{
select: ' countryId',
selectData: Liferay.Address.getCountries,
selectDesc: 'nameCurrentValue',
selectId: 'countryId',
selectSort: '<%= true %>',
selectVal: '<%= countryId %>'
},
{
select: ' regionId',
selectData: Liferay.Address.getRegions,
selectDesc: 'name',
selectDisableOnEmpty: true,
selectId: 'regionId',
selectVal: '<%= regionId %>'
}
]
);
\end{verbatim}
\chapter{FreeMarker Taglib Macros}\label{freemarker-taglib-macros}
Liferay DXP's taglibs are mapped to FreeMarker macros, so you can use
them in your FreeMarker templates. See the
\href{/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs}{Taglib
reference} for more information on using each taglib in your theme
templates. The taglib macros are defined in
\texttt{taglib-mappings.properties} files. For convenience, these macros
are listed in the table below:
Macro
Taglib
TLD
\texttt{liferay\_aui}
liferay-aui
liferay-aui.tld
\texttt{liferay\_portlet}
liferay-portlet
liferay-portlet-ext.tld
\texttt{liferay\_security}
liferay-security
liferay-security.tld
\texttt{liferay\_theme}
liferay-theme
liferay-theme.tld
\texttt{liferay\_ui}
liferay-ui
liferay-ui.tld
\texttt{liferay\_util}
liferay-util
liferay-util.tld
\texttt{portlet}
portlet
liferay-portlet.tld
\texttt{liferay\_frontend}
liferay-frontend
liferay-frontend.tld
\texttt{clay}
clay
liferay-clay.tld
\texttt{liferay\_map}
liferay-map
liferay-map.tld
\texttt{liferay\_rss}
liferay-rss
liferay-rss.tld
\texttt{liferay\_flags}
liferay-flags
liferay-flags.tld
\texttt{liferay\_expando}
liferay-expando
liferay-expando.tld
\texttt{liferay\_journal}
liferay-journal
liferay-journal.tld
\texttt{liferay\_social\_bookmarks}
liferay-social-bookmarks
liferay-social-bookmarks.tld
\texttt{liferay\_site}
liferay-site
liferay-site.tld
\texttt{liferay\_comment}
liferay-comment
liferay-comment.tld
\texttt{liferay\_social\_activities}
liferay-social-activities
liferay-social-activities.tld
\texttt{liferay\_asset}
liferay-asset
liferay-asset.tld
\texttt{liferay\_trash}
liferay-trash
liferay-trash.tld
\texttt{liferay\_item\_selector}
liferay-item-selector
liferay-item-selector.tld
\texttt{liferay\_layout}
liferay-layout
liferay-layout.tld
\texttt{liferay\_editor}
liferay-editor
liferay-editor.tld
\texttt{liferay-fragment}
liferay-fragment
liferay-fragment.tld
\texttt{liferay\_reading\_time}
liferay-reading-time
liferay-reading-time.tld
\texttt{liferay\_sharing}
liferay-sharing
liferay-sharing.tld
\texttt{liferay\_site\_navigation}
liferay-site-navigation
liferay-site-navigation.tld
\texttt{adaptive\_media\_image}
liferay-adaptive-media
liferay-adaptive-media.tld
\texttt{liferay\_product\_navigation}
liferay-product-navigation
liferay-product-navigation.tld
\chapter{Setting up Your npm
Environment}\label{setting-up-your-npm-environment}
If you're using npm for development in Liferay DXP, you should set up
your npm environment to avoid potential permissions issues. Follow these
steps to configure your npm environment:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an \texttt{.npmrc} file in your user's home directory. This
helps bypass npm permission-related issues.
\item
In the \texttt{.npmrc} file, specify a \texttt{prefix} property based
on your user's home directory, like the one shown below. This value
specifies where to install global npm packages:
\begin{verbatim}
prefix=/Users/[username]/.npm-packages
\end{verbatim}
\item
Set the \texttt{NPM\_PACKAGES} system environment variable to the
\texttt{prefix} value you just specified:
\begin{verbatim}
NPM_PACKAGES=/Users/[username]/.npm-packages (same as prefix value)
\end{verbatim}
\item
Since npm installs Yeoman and gulp executables to
\texttt{\$\{NPM\_PACKAGES\}/bin} on UNIX and to
\texttt{\%NPM\_PACKAGES\%} on Windows, make sure to add the
appropriate directory to your system path. For example, on UNIX you'd
set this:
\begin{verbatim}
PATH=${PATH}:${NPM_PACKAGES}/bin
\end{verbatim}
\end{enumerate}
\chapter{Sitemap Page Configuration
Options}\label{sitemap-page-configuration-options}
If you're importing resources with your themes, you must define the
pages for the site in the theme's \texttt{sitemap.json}. Below is the
full list of available configuration options for pages in the theme's
\texttt{sitemap.json}:
\textbf{colorSchemeId:} Specifies a different color scheme (by ID) than
the default color scheme to use for the page.
\textbf{columns:} Specifies the column contents for the page.
\textbf{friendlyURL:} Sets the page's friendly URL.
\textbf{hidden:} Sets whether the page is hidden.
\textbf{layoutCss:} Sets custom CSS for the page to load after the
theme.
\textbf{layoutPrototypeLinkEnabled:} Sets whether the page inherits
changes made to the page template (if the page has one).
\textbf{layoutPrototypeName:} Specifies the page template (by name) to
use for the page. If this is defined, the page template's UUID is
retrieved using the name, and \texttt{layoutPrototypeUuid} is not
required.
\textbf{layoutPrototypeUuid:} Specifies the page template (by UUID) to
use for the page. If \texttt{layoutPrototypeName} is defined, this is
not required.
\textbf{layouts:} Specifies child pages for a page set.
\textbf{name:} The page's name.
\textbf{nameMap:} Passes a name object with multiple name key/value
pairs. You can use this to pass translations for a page's title, as
shown in the example above.
\textbf{privatePages:} Specifies private pages.
\textbf{publicPages:} Specifies public pages.
\textbf{themeId:} Specifies a different theme (by ID) than the default
theme bundled with the \texttt{sitemap.json} to use for the page.
\textbf{title:} The page's title.
\textbf{type:} Sets the page type. The default value is \texttt{portlet}
(empty page). Possible values are \texttt{copy} (copy of a page of this
site), \texttt{embedded}, \texttt{full\_page\_application},
\texttt{link\_to\_layout}, \texttt{node} (page set), \texttt{panel},
\texttt{portlet}, and \texttt{url} (link to URL).
\textbf{typeSettings:} Specifies settings (using key/value pairs) for
the page \texttt{type}.
\chapter{CKEditor Plugin Reference
Guide}\label{ckeditor-plugin-reference-guide}
This reference guide provides a list of the default CKEditor plugins
bundled with Liferay DXP's AlloyEditor. You can
\href{/docs/7-2/frameworks/-/knowledge_base/f/adding-buttons-to-alloyeditor-toolbars}{use
these existing CKEditor plugins in your custom AlloyEditor
configurations}. Each plugin below links to its \texttt{plugin.js} file
for reference, specifying the plugin's name and buttons if applicable:
\begin{itemize}
\tightlist
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/about/plugin.js}{about}
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/a11yhelp/plugin.js}{allyhelp}
\item
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/a11yhelpbtn/plugin.js}{allyhelpbtn}
\item
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/ajaxsave/plugin.js}{ajaxsave}
\item
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/autocomplete/plugin.js}{autocomplete}
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/basicstyles/plugin.js}{basicstyles}
\item
\href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/bbcode/plugin.js}{bbcode}
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/bidi/plugin.js}{bidi}
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/blockquote/plugin.js}{blockquote}
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/clipboard/plugin.js}{clipboard}
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/colorbutton/plugin.js}{colorbutton}
\item
\href{https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/colordialog/plugin.js}{colordialog}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/contextmenu/plugin.js}{contextmenu}
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/creole/plugin.js}{creole}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/dialogadvtab/plugin.js}{dialogadvtab}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/div/plugin.js}{div}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/elementspath/plugin.js}{elementspath}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/enterkey/plugin.js}{enterkey}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/entities/plugin.js}{entities}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/filebrowser/plugin.js}{filebrowse}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/find/plugin.js}{find}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/flash/plugin.js}{flash}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/floatingspace/plugin.js}{floatingspace}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/font/plugin.js}{font}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/format/plugin.js}{format}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/forms/plugin.js}{forms}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/horizontalrule/plugin.js}{horizontalrule}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/htmlwriter/plugin.js}{htmlwriter}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/image/plugin.js}{image}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/iframe/plugin.js}{iframe}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/indent/plugin.js}{indent}
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/itemselector/plugin.js}{itemselector}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/justify/plugin.js}{justify}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/link/plugin.js}{link}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/list/plugin.js}{list}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/liststyle/plugin.js}{liststyle}
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/lfrpopup/plugin.js}{lfrpopup}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/magicline/plugin.js}{magicline}
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/media/plugin.js}{media}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/newpage/plugin.js}{newpage}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/pagebreak/plugin.js}{pagebreak}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/pastefromword/plugin.js}{pastefromword}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/pastetext/plugin.js}{pastetext}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/preview/plugin.js}{preview}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/removeformat/plugin.js}{removeformat}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/resize/plugin.js}{resize}
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/restore/plugin.js}{restore}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/selectall/plugin.js}{selectall}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/showblocks/plugin.js}{showblocks}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/showborders/plugin.js}{showborders}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/smiley/plugin.js}{smiley}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/sourcearea/plugin.js}{sourcearea}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/specialchar/plugin.js}{specialchar}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/stylescombo/plugin.js}{stylescombo}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/tab/plugin.js}{tab}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/table/plugin.js}{table}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/tabletools/plugin.js}{tabletools}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/templates/plugin.js}{templates}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/toolbar/plugin.js}{toolbar}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/undo/plugin.js}{undo}
\item
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-editor/frontend-editor-ckeditor-web/src/main/resources/META-INF/resources/_diffs/plugins/wikilink/plugin.js}{wikilink}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/wysiwygarea/plugin.js}{wysiwygarea}
\end{itemize}
\noindent\hrulefill
\textbf{Note:} The following CKEditor plugins are not available for
inline mode in AlloyEditor at this time, but you can still use them in
the classic CKEditor:
\begin{itemize}
\tightlist
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/maximize/plugin.js}{maximize}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/print/plugin.js}{print}
\item
\href{https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/save/plugin.js}{save}
\end{itemize}
To use the Classic CKEditor instead of AlloyEditor, there are a few
properties to set, depending on the portlet. Add the
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/portal-impl/src/portal.properties\#L5432-L5441}{properties}
that you need to your \texttt{portal-ext.properties} file:
\begin{verbatim}
editor.wysiwyg.default=ckeditor
editor.wysiwyg.portal-impl.portlet.ddm.text_html.ftl=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.announcements.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.blogs.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit_message.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_message.html.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.discussion.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.email_notification_settings.jsp=ckeditor
\end{verbatim}
\chapter{Fully Qualified Portlet IDs}\label{fully-qualified-portlet-ids}
Below is a listing of the portlet IDs for the default portlets in
Liferay DXP. You can use these IDs to embed portlets in your theme's
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-portlets-in-a-sitemap}{sitemap}.
\textbf{Collaboration}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Blogs & \texttt{com\_liferay\_blogs\_web\_portlet\_BlogsPortlet} \\
Blogs Aggregator &
\texttt{com\_liferay\_blogs\_web\_portlet\_BlogsAgreggatorPortlet} \\
Calendar &
\texttt{com\_liferay\_calendar\_web\_portlet\_CalendarPortlet} \\
Dynamic Data Lists Display &
\texttt{com\_liferay\_dynamic\_data\_lists\_web\_portlet\_DDLDisplayPortlet} \\
Form &
\texttt{com\_liferay\_dynamic\_data\_mapping\_form\_web\_portlet\_DDMFormPortlet} \\
Invite Members &
\texttt{com\_liferay\_invitation\_invite\_members\_web\_portlet\_InviteMembersPortlet} \\
Message Boards &
\texttt{com\_liferay\_message\_boards\_web\_portlet\_MBPortlet} \\
Recent Bloggers &
\texttt{com\_liferay\_blogs\_recent\_bloggers\_web\_portlet\_RecentBloggersPortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{Community}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
My Sites &
\texttt{com\_liferay\_site\_my\_sites\_web\_portlet\_MySitesPortlet} \\
Page Comments &
\texttt{com\_liferay\_comment\_page\_comments\_web\_portlet\_PageCommentsPortlet} \\
Page Flags &
\texttt{com\_liferay\_flags\_web\_portlet\_PageFlagsPortlet} \\
Page Ratings &
\texttt{com\_liferay\_ratings\_page\_ratings\_web\_portlet\_PageRatingsPortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{Content Management}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Asset Publisher &
\texttt{com\_liferay\_asset\_publisher\_web\_portlet\_AssetPublisherPortlet} \\
Breadcrumb &
\texttt{com\_liferay\_site\_navigation\_breadcrumb\_web\_portlet\_SiteNavigationBreadcrumbPortlet} \\
Categories Navigation &
\texttt{com\_liferay\_asset\_categories\_navigation\_web\_portlet\_AssetCategoriesNavigationPortlet} \\
Documents and Media &
\texttt{com\_liferay\_document\_library\_web\_portlet\_DLPortlet} \\
Highest Rated Assets &
\texttt{com\_liferay\_asset\_publisher\_web\_portlet\_HighestRatedAssetsPortlet} \\
Knowledge Base Article &
\texttt{com\_liferay\_knowledge\_base\_web\_portlet\_ArticlePortlet} \\
Knowledge Base Display &
\texttt{com\_liferay\_knowledge\_base\_web\_portlet\_DisplayPortlet} \\
Knowledge Base Search &
\texttt{com\_liferay\_knowledge\_base\_web\_portlet\_SearchPortlet} \\
Knowledge Base Section &
\texttt{com\_liferay\_knowledge\_base\_web\_portlet\_SectionPortlet} \\
Media Gallery &
\texttt{com\_liferay\_document\_library\_web\_portlet\_IGDisplayPortlet} \\
Most Viewed Assets &
\texttt{com\_liferay\_asset\_publisher\_web\_portlet\_MostViewedAssetsPortlet} \\
Navigation Menu &
\texttt{com\_liferay\_site\_navigation\_menu\_web\_portlet\_SiteNavigationMenuPortlet} \\
Nested Applications &
\texttt{com\_liferay\_nested\_portlets\_web\_portlet\_NestedPortletsPortlet} \\
Polls Display Portlet &
\texttt{com\_liferay\_polls\_web\_portlet\_PollsDisplayPortlet} \\
Related Assets &
\texttt{com\_liferay\_asset\_publisher\_web\_portlet\_RelatedAssetsPortlet} \\
Site Map &
\texttt{com\_liferay\_site\_navigation\_site\_map\_web\_portlet\_SiteNavigationSiteMapPortlet} \\
Sites Directory &
\texttt{com\_liferay\_site\_navigation\_directory\_web\_portlet\_SitesDirectoryPortlet} \\
Tag Cloud &
\texttt{com\_liferay\_asset\_tags\_navigation\_web\_portlet\_AssetTagsCloudPortlet} \\
Tags Navigation &
\texttt{com\_liferay\_asset\_tags\_navigation\_web\_portlet\_AssetTagsNavigationPortlet} \\
Web Content Display &
\texttt{com\_liferay\_journal\_content\_web\_portlet\_JournalContentPortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{News}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Alerts &
\texttt{com\_liferay\_announcements\_web\_portlet\_AlertsPortlet} \\
Announcements &
\texttt{com\_liferay\_announcements\_web\_portlet\_AnnouncementsPortlet} \\
Recent Content Portlet &
\texttt{com\_liferay\_asset\_publisher\_web\_portlet\_RecentContentPortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{Sample}
\noindent\hrulefill
\begin{longtable}[]{@{}ll@{}}
\toprule\noalign{}
Portlet & ID \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Hello World &
\texttt{com\_liferay\_hello\_world\_web\_portlet\_HelloWorldPortlet} \\
IFrame & \texttt{com\_liferay\_iframe\_web\_portlet\_IFramePortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{Search}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Category Facet &
\texttt{com\_liferay\_portal\_search\_web\_category\_facet\_portlet\_CategoryFacetPortlet} \\
Custom Facet &
\texttt{com\_liferay\_portal\_search\_web\_custom\_facet\_portlet\_CustomFacetPortlet} \\
Folder Facet &
\texttt{com\_liferay\_portal\_search\_web\_folder\_facet\_portlet\_FolderFacetPortlet} \\
Modified Facet &
\texttt{com\_liferay\_portal\_search\_web\_modified\_facet\_portlet\_ModifiedFacetPortlet} \\
Search Bar &
\texttt{com\_liferay\_portal\_search\_web\_search\_bar\_portlet\_SearchBarPortlet} \\
Search Insights &
\texttt{com\_liferay\_portal\_search\_web\_search\_insights\_portlet\_SearchInsightsPortlet} \\
Search Options &
\texttt{com\_liferay\_portal\_search\_web\_search\_options\_portlet\_SearchOptionsPortlet} \\
Search Results &
\texttt{com\_liferay\_portal\_search\_web\_search\_results\_portlet\_SearchResultsPortlet} \\
Site Facet &
\texttt{com\_liferay\_portal\_search\_web\_site\_facet\_portlet\_SiteFacetPortlet} \\
Suggestions &
\texttt{com\_liferay\_portal\_search\_web\_suggestions\_portlet\_SuggestionsPortlet} \\
Tag Facet &
\texttt{com\_liferay\_portal\_search\_web\_tag\_facet\_portlet\_TagFacetPortlet} \\
Type Facet &
\texttt{com\_liferay\_portal\_search\_web\_type\_facet\_portlet\_TypeFacetPortlet} \\
User Facet &
\texttt{com\_liferay\_portal\_search\_web\_user\_facet\_portlet\_UserFacetPortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{Social}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Activities &
\texttt{com\_liferay\_social\_activities\_web\_portlet\_SocialActivitiesPortlet} \\
Contacts Center &
\texttt{com\_liferay\_contacts\_web\_portlet\_ContactsCenterPortlet} \\
Members &
\texttt{com\_liferay\_social\_networking\_web\_members\_portlet\_MembersPortlet} \\
My Contacts &
\texttt{com\_liferay\_contacts\_web\_portlet\_MyContactsPortlet} \\
Profile &
\texttt{com\_liferay\_contacts\_web\_portlet\_ProfilePortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{Tools}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Language Selector &
\texttt{com\_liferay\_site\_navigation\_language\_web\_portlet\_SiteNavigationLanguagePortlet} \\
Search &
\texttt{com\_liferay\_portal\_search\_web\_portlet\_SearchPortlet} \\
Sign In & \texttt{com\_liferay\_login\_web\_portlet\_LoginPortlet} \\
\end{longtable}
\noindent\hrulefill
\textbf{Wiki}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Portlet
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
ID
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Page Menu &
\texttt{com\_liferay\_wiki\_navigation\_web\_portlet\_WikiNavigationPageMenuPortlet} \\
Tree Menu &
\texttt{com\_liferay\_wiki\_navigation\_web\_portlet\_WikiNavigationTreeMenuPortlet} \\
Wiki & \texttt{com\_liferay\_wiki\_web\_portlet\_WikiPortlet} \\
Wiki Display &
\texttt{com\_liferay\_wiki\_web\_portlet\_WikiDisplayPortlet} \\
\end{longtable}
\chapter{Available SPA Lifecycle
Events}\label{available-spa-lifecycle-events}
During development, you may need to know when navigation has started or
stopped in your SPA. SennaJS makes this easy by exposing lifecycle
events that represent state changes in the application. The available
lifecycle events are listed in the table below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Event
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Ex payload
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{beforeNavigate} & Fires before navigation starts. This event
passes a JSON object with the path to the content you are navigating to
and whether to update the history. &
\texttt{\{\ path:\ \textquotesingle{}/pages/page1.html\textquotesingle{},\ replaceHistory:\ false\ \}} \\
\texttt{startNavigate} & Fires when navigation begins & `\{ form: ' \\
\end{longtable}
\noindent\hrulefill replaceHistory: false \}` \textbar{}
\noindent\hrulefill
\texttt{endNavigate} \textbar{} Fired after the content has been
retrieved and inserted onto
\noindent\hrulefill the page \textbar{}
\texttt{\{\ form:\ \textquotesingle{}\textless{}form\ name="form"\textgreater{}\textless{}/form\textgreater{}\textquotesingle{},\ path:\ \textquotesingle{}/pages/page1.html\textquotesingle{}\ \}}
\textbar{}
These events can be leveraged easily by listening for them on the
Liferay global object. For example, the JavaScript below alerts the user
to ``Get ready to navigate to'' the URL that has been clicked, just
before SPA navigation begins:
\begin{verbatim}
Liferay.on('beforeNavigate', function(event) {
alert("Get ready to navigate to " + event.path);
});
\end{verbatim}
The alert takes advantage of the payload for the \texttt{beforeNavigate}
event, retrieving the URL from the \texttt{path} attribute of the JSON
payload object.
\begin{figure}
\centering
\includegraphics{./images/private-messaging-before-navigate.png}
\caption{You can leverage SPA lifecycle events in your apps.}
\end{figure}
\chapter{Theme Anatomy Reference
Guide}\label{theme-anatomy-reference-guide}
A theme is made up of several files. Although most of the files are
named after their matching components, their functions may be unclear.
This reference guide explains each file's usage to make clear which
files to modify.
Themes built with the
\href{https://github.com/liferay/liferay-js-themes-toolkit/tree/master/packages}{Liferay
JS Theme Toolkit} have the anatomy shown below:
\begin{itemize}
\tightlist
\item
\texttt{theme-name/}
\begin{itemize}
\tightlist
\item
\texttt{src/}
\begin{itemize}
\tightlist
\item
\texttt{css/}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#-clay-customscss}{\texttt{\_clay\_custom.scss}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#-clay-variablesscss}{\texttt{\_clay\_variables.scss}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#-customscss}{\texttt{\_custom.scss}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#-liferay-variables-customscss}{\texttt{\_liferay\_variables\_custom.scss}}
\end{itemize}
\item
\texttt{images/}
\begin{itemize}
\tightlist
\item
(custom images)
\end{itemize}
\item
\texttt{js/}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#mainjs}{\texttt{main.js}}
\end{itemize}
\item
\texttt{templates/}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#init-customftl}{\texttt{init\_custom.ftl}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#navigationftl}{\texttt{navigation.ftl}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#portal-normalftl}{\texttt{portal\_normal.ftl}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#portal-pop-upftl}{\texttt{portal\_pop\_up.ftl}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#portletftl}{\texttt{portlet.ftl}}
\end{itemize}
\item
\texttt{WEB-INF/}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#liferay-look-and-feelxml}{\texttt{liferay-look-and-feel.xml}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#liferay-plugin-packageproperties}{\texttt{liferay-plugin-package.properties}}
\item
\texttt{src/}
\begin{itemize}
\tightlist
\item
\texttt{resources-importer/}
\begin{itemize}
\tightlist
\item
(Many directories)
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#liferay-themejson}{\texttt{liferay-theme.json}}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide\#packagejson}{\texttt{package.json}}
\end{itemize}
\end{itemize}
Regarding CSS files, you should only modify
\texttt{\_clay\_custom.scss}, \texttt{\_clay\_variables.scss},
\texttt{\_custom.scss}, and \texttt{\_liferay\_variables\_custom.scss}.
You can of course overwrite any CSS file you want, but if you modify any
other files, you're removing styling that 7.0 needs to work properly.
\section{Theme Files}\label{theme-files}
\section{\_clay\_custom.scss}\label{clay_custom.scss}
Used for Clay custom styles, i.e.~styles for a third party Bootstrap
theme. Anything written in this file is compiled in the same scope as
Bootstrap/Lexicon, so you can use their variables, mixins, etc. You can
also implement any of the variables you define in
\texttt{\_clay\_variables.scss}.
\section{\_clay\_variables.scss}\label{clay_variables.scss}
Used to store custom Sass variables. This file gets injected into the
Bootstrap/Lexicon build, so you can overwrite variables and change how
those libraries are compiled.
\section{\_custom.scss}\label{custom.scss}
Used for custom CSS styles. You should place all of your custom CSS
modifications in this file.
\section{\_liferay\_variables\_custom.scss}\label{liferay_variables_custom.scss}
Used for overwriting variables defined in
\texttt{\_liferay\_variables.scss} without wiping out the whole file.
\section{init\_custom.ftl}\label{init_custom.ftl}
Used for custom FreeMarker variables i.e.~
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings}{theme
setting} variables.
\section{navigation.ftl}\label{navigation.ftl}
The theme template for the theme's navigation.
\section{portal\_normal.ftl}\label{portal_normal.ftl}
Similar to a static site's \texttt{index.html}, this file acts as a hub
for all theme templates.
\section{portal\_pop\_up.ftl}\label{portal_pop_up.ftl}
The theme template for pop up dialogs for the theme's portlets.
\section{portlet.ftl}\label{portlet.ftl}
The theme template for the theme's portlets. If your theme uses
\href{/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets\#portlet-decorators}{Application
Decorators}, you can modify this file to create application
decorator-specific theme settings.
\section{liferay-theme.json}\label{liferay-theme.json}
Contains the configuration settings for your app server, in Node.js
tool-based themes. You can change this file manually at any time to
update your server settings. The file can also be updated via the
\href{/docs/7-2/frameworks/-/knowledge_base/f/updating-your-themes-app-server}{\texttt{gulp\ init}
task}.
\section{package.json}\label{package.json}
Contains theme setting information such as the theme template language,
version, and base theme, for Node.js tool developed themes. You can
update this file manually. The
\href{/docs/7-2/frameworks/-/knowledge_base/f/changing-your-base-theme}{\texttt{gulp\ extend}
task} can also be used to change the base theme.
\section{main.js}\label{main.js}
Used for custom JavaScript.
\section{liferay-look-and-feel.xml}\label{liferay-look-and-feel.xml}
Contains basic information for the theme. If your theme has
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings}{theme
settings}, they are defined in this file. For a full explanation of this
file, please see the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-look-and-feel_7_2_0.dtd.html}{Definitions
docs}.
\section{liferay-plugin-package.properties}\label{liferay-plugin-package.properties}
Contains general properties for the theme.
\href{/docs/7-2/frameworks/-/knowledge_base/f/importing-resources-with-a-theme}{Resources
Importer} configuration settings are also placed in this file. For a
full explanation of the properties available for this file please see
the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/liferay-plugin-package_7_2_0.properties.html}{7.2
Properties documentation}.
\chapter{Freemarker Variable Reference
Guide}\label{freemarker-variable-reference-guide}
By default, FreeMarker templates have access to several variables
defined in
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-theme/frontend-theme-unstyled/src/main/resources/META-INF/resources/_unstyled/templates/init.ftl}{\texttt{init.ftl}}
that you can use in your
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{themes}
to access several theme objects, settings, and resources. Several of
these variables are listed below for reference:
\textbf{Common Variables}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{theme\_display} & Returns the \texttt{themeDisplay} Java Object
and all its methods \\
\texttt{portlet\_display} & Returns the \texttt{portletDisplay} Java
Object and all its methods \\
\texttt{layoutSet} & Returns the page set \\
\texttt{theme\_timestamp} & Prints the date in the current locale with
the given format \\
\texttt{theme\_settings} & Retrieves theme settings. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings}{configurable
theme settings} for more information. \\
\texttt{root\_css\_class} & Returns the root CSS class which indicates
the direction of the page (\texttt{ltr} (left-to-right) by default) \\
\texttt{css\_class} & Returns a string of the current classes applied to
the body of the page \\
\texttt{page\_group} & Retrieves the page group \\
\texttt{css\_folder} & Returns the path to the theme's \texttt{css}
folder \\
\texttt{images\_folder} & Returns the path to the theme's
\texttt{images} folder \\
\texttt{javascript\_folder} & Returns the path to the theme's
\texttt{javascript} folder \\
\texttt{templates\_folder} & Returns the path to the theme's
\texttt{templates} folder \\
\texttt{full\_css\_path} & Returns the full path, which includes the
servlet context, to the theme's \texttt{css} \\
\texttt{full\_templates\_path} & returns the full path, which includes
the servlet context, to the theme's \texttt{templates} \\
\texttt{css\_main\_file} & Returns the path to \texttt{main.css} \\
\texttt{js\_main\_file} & Returns the path to \texttt{main.js} \\
\texttt{company\_id} & Returns the company ID \\
\texttt{company\_name} & Returns the company name \\
\texttt{company\_logo} & Returns the company logo's URL \\
\texttt{company\_logo\_height} & Returns the company logo's height \\
\texttt{company\_logo\_width} & Returns the company logo's width \\
\texttt{company\_url} & Returns the URL of the home page for the
company \\
\texttt{time\_zone} & Returns the time zone for the current user \\
\texttt{is\_login\_redirect\_required} & Returns whether a login
redirect is required for the user \\
\texttt{is\_signed\_in} & Returns whether the user is signed in \\
\texttt{group\_id} & Returns the group ID for the current user \\
\texttt{time\_zone} & Returns the time zone for the current user \\
\texttt{is\_default\_user} & Returns if the user has a default role \\
\texttt{is\_female} & Returns if the current user is Female \\
\texttt{is\_male} & Returns if the current user is Male \\
\texttt{is\_setup\_complete} & Returns whether the user has configured
their profile \\
\texttt{language} & Returns the native language for the current user \\
\texttt{language\_id} & Returns the ID of the current locale \\
\texttt{user\_birthday} & Returns the current user's birthday \\
\texttt{user\_comments} & Returns comments from the user's profile \\
\texttt{user\_email\_address} & Returns the user's email address \\
\texttt{user\_first\_name} & Returns the user's first name \\
\texttt{user\_greeting} & Returns the user's greeting \\
\texttt{user\_id} & Returns the ID of the current user \\
\texttt{user\_last\_login\_ip} & Returns the IP address that the user
last logged in from \\
\texttt{user\_last\_name} & Returns the last name of the current user \\
\texttt{user\_login\_ip} & Returns the current user's current IP
address \\
\texttt{user\_middle\_name} & Returns the user's middle name \\
\texttt{user\_name} & Returns the current user's username \\
\texttt{w3c\_language\_id} & Returns the W3C language code of the
current language \\
\end{longtable}
\noindent\hrulefill
\textbf{URLs}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{show\_control\_panel} & Returns whether the current user has
permission to view the Control Panel \\
\texttt{control\_panel\_text} & Returns the ``control-panel'' language
key in the current user's locale, if they have permission to view the
Control Panel \\
\texttt{control\_panel\_url} & Returns the URL to the Control Panel, if
the current user has permission to view the Control Panel \\
\texttt{show\_home} & Returns whether the current user is on a page \\
\texttt{home\_text} & Returns the ``home'' language key in the current
user's locale \\
\texttt{home\_url} & Returns the URL to the home page \\
\texttt{show\_my\_account} & Returns whether the current user's account
icon is visible \\
\texttt{my\_account\_text} & Returns the ``my-account'' language key in
the current user's locale, if the user's account icon is visible \\
\texttt{my\_account\_url} & Returns the URL to the user's Account
Settings page if the user's account icon is visible \\
\texttt{show\_sign\_in} & Returns whether the sign in link is visible \\
\texttt{sign\_in\_text} & Returns the ``sign-in'' language key in the
current user's locale, if they are signed out \\
\texttt{sign\_in\_url} & Returns the sign in URL, if the current user is
signed out \\
\texttt{show\_sign\_out} & Returns whether the sign out link is
visible \\
\texttt{sign\_out\_text} & Returns the ``sign-out'' language key in the
current user's locale, if they are signed in \\
\texttt{sign\_out\_url} & Returns the sign out URL, if the current user
is signed in \\
\end{longtable}
\noindent\hrulefill
\textbf{Page}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{the\_title} & Returns the current page's title \\
\texttt{selectable} & Returns whether the current page is selectable \\
\texttt{is\_maximized} & Returns whether the page is maximized \\
\texttt{page} & Returns the current page (layout) \\
\texttt{is\_first\_child} & Returns whether the current page is the
first child page in the navigation \\
\texttt{is\_first\_parent} & Returns whether the current page is the
first parent page in the navigation \\
\texttt{is\_portlet\_page} & Returns whether the current page is a
widget page (portlet) \\
\texttt{site\_name} & Returns the site's name \\
\texttt{is\_guest\_group} & Returns whether the current page group is
for guests \\
\texttt{site\_type} & Returns the type of the current site: site,
company site, organization site, or user site \\
\texttt{site\_default\_url} & Returns the default URL for the site \\
\texttt{layout\_friendly\_url} & Returns the friendly URL of the current
page \\
\texttt{portlet\_id} & Returns the portlet ID for the specified
portlet \\
\end{longtable}
\noindent\hrulefill
\textbf{Logo}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{logo\_css\_class} & Returns a string of the current classes
applied to the logo. \\
\texttt{use\_company\_logo} & Returns whether the logo is displayed \\
\texttt{site\_logo\_height} & Returns the logo's height \\
\texttt{site\_logo\_width} & Returns the logo's width \\
\texttt{show\_site\_name\_supported} & Returns whether the logo is
configured to show the site name. The value is \texttt{true} if
\texttt{show\_site\_name\_default} is true. \\
\texttt{show\_site\_name\_default} & Returns whether the Show Site Name
Default theme setting is enabled \\
\texttt{show\_site\_name} & Returns whether the \texttt{showSiteName}
property for the current pageset is enabled \\
\texttt{logo\_description} & Returns the Site's name or nothing if
\texttt{show\_site\_name} is enabled. It is used for alternate text for
the logo by default. \\
\end{longtable}
\noindent\hrulefill
\textbf{Navigation}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{has\_navigation} & Returns whether navigation exists (i.e.~at
least one page exists) \\
\texttt{nav\_items} & Returns the current pages as list \\
\texttt{nav\_css\_class} & Returns a string of the current classes
applied to the page's navigation \\
\end{longtable}
\noindent\hrulefill
\textbf{My Sites}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{show\_my\_sites} & Returns whether the current user has a My
Sites page \\
\texttt{show\_my\_places} & Returns whether the current user has a My
Sites page \\
\texttt{my\_sites\_text} & Returns the ``my-sites'' language key in the
current user's locale \\
\texttt{my\_places\_text} & Returns whether the current user has a My
Sites page \\
\end{longtable}
\noindent\hrulefill
\textbf{Includes}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{dir\_include} & Returns ``/html'' \\
\texttt{body\_bottom\_include} & Returns
``\({dir_include}/common/themes/body_bottom.jsp" |
`body_top_include` | Returns "\)\{dir\_include\}/common/themes/body\_top.jsp'' \\
\texttt{bottom\_include} & Returns
``\({dir_include}/common/themes/bottom.jsp" |
`top_head_include` | Returns "\)\{dir\_include\}/common/themes/top\_head.jsp'' \\
\texttt{top\_messages\_include} & Returns
``\$\{dir\_include\}/common/themes/top\_messages.jsp'' \\
\end{longtable}
\noindent\hrulefill
\textbf{Date}
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Variable
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{date} & Gives access to the \texttt{dateUtil} Java Object and
all its methods \\
\texttt{current\_time} & Returns the current time \\
\texttt{the\_year} & Returns the current year \\
\end{longtable}
\chapter{Gradle Plugins}\label{gradle-plugins}
Liferay provides plugins that you can apply to your Gradle project. This
reference documentation describes how to apply and use Liferay's Gradle
plugins.
\textbf{Important:} If you're using
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} to create Liferay apps, most of the Liferay Gradle plugins
covered in this section are already applied by default. The
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-workspace}{com.liferay.gradle.plugins.workspace}
and
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins}{com.liferay.gradle.plugins}
dependencies provide them, both of which are preset in workspace by
default.
Do not apply a Liferay Gradle plugin to an app that already has access
to it.
Each article in this section describes how to apply the plugin, what
Gradle tasks the plugin provides, the plugin's configuration properties,
and the plugin's dependencies.
\chapter{App Javadoc Builder Gradle
Plugin}\label{app-javadoc-builder-gradle-plugin}
The App Javadoc Builder Gradle plugin lets you generate API
documentation as a single, combined HTML document for an application
that spans different subprojects, each one representing a different
component of the same application.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage}
To use the plugin, include it in the build script of the root project:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.app.javadoc.builder", version: "1.2.2"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.app.javadoc.builder"
\end{verbatim}
The App Javadoc Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/standard_plugins.html\#N135C1}{\texttt{base}}
and \texttt{reporting-base} plugins.
\section{Project Extension}\label{project-extension}
The App Javadoc Builder plugin exposes the following properties through
the extension named \texttt{appJavadocBuilder}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{copyTags} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to copy the custom block tags
configuration from the subprojects. It sets the Javadoc
\href{http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html\#tag}{\texttt{-tag}}
argument for the \hyperref[appjavadoc]{\texttt{appJavadoc}} task.
\texttt{doclintDisabled} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} on JDK8+, \texttt{false} otherwise. \textbar{} Whether to
ignore Javadoc errors. It sets the Javadoc
\href{docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html\#BEJEFABE}{\texttt{-Xdoclint}}
and
\href{http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html\#CHDGFHAA}{\texttt{-quiet}}
arguments for the \hyperref[appjavadoc]{\texttt{appJavadoc}} task.
\texttt{groupNameClosure} \textbar{}
\texttt{Closure\textless{}String\textgreater{}} \textbar{} The
subproject's description, or the subproject's name if the description is
empty. \textbar{} The closure invoked in order to get the group heading
for a subproject. The given closure is passed a
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html}{\texttt{Project}}
as its parameter. If \texttt{groupPackages} is \texttt{false}, this
property is not used. \texttt{groupPackages} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} \textbar{} Whether to separate packages on the
overview page based on the subprojects they belong to. It sets the
\href{docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html\#CHDIGGII}{\texttt{-group}}
argument for the \hyperref[appjavadoc]{\texttt{appJavadoc}} task.
\texttt{subprojects} \textbar{}
\texttt{Set\textless{}Project\textgreater{}} \textbar{}
\texttt{project.subprojects} \textbar{} The subprojects to include in
the API documentation of the app.
The same extension exposes the following methods:
Method \textbar{} Description
\texttt{AppJavadocBuilderExtension\ onlyIf(Closure\textless{}Boolean\textgreater{}\ onlyIfClosure)}
\textbar{} Includes a subproject in the API documentation if the given
closure returns \texttt{true}. The closure is evaluated at the end of
the subproject configuration phase and is passed a single parameter: the
subproject. If the closure returns \texttt{false}, the subproject is not
included in the API documentation.
\texttt{AppJavadocBuilderExtension\ onlyIf(Spec\textless{}Project\textgreater{}\ onlyIfSpec)}
\textbar{} Includes a subproject in the API documentation if the given
spec is satisfied. The spec is evaluated at the end of the subproject
configuration phase. If the spec is not satisfied, the subproject is not
included in the API documentation.
\texttt{AppJavadocBuilderExtension\ subprojects(Iterable\textless{}Project\textgreater{}\ subprojects)}
\textbar{} Include additional projects in the API documentation of the
app.
\texttt{AppJavadocBuilderExtension\ subprojects(Project...\ subprojects)}
\textbar{} Include additional projects in the API documentation of the
app.
\section{Tasks}\label{tasks}
The plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{appJavadoc} \textbar{} The \texttt{javadoc} tasks of the
subprojects. \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html}{\texttt{Javadoc}}
\textbar{} Generates Javadoc API documentation for the app.
\texttt{jarAppJavadoc} \textbar{} \texttt{appJavadoc} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html}{\texttt{Jar}}
\textbar{} Assembles a JAR archive containing the Javadoc files for this
app.
The \texttt{appJavadoc} task is automatically configured with sensible
defaults:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html\#org.gradle.api.tasks.javadoc.Javadoc:classpath}{\texttt{classpath}}
\textbar{} The \texttt{javadoc.classpath} of all the subprojects.
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html\#org.gradle.api.tasks.javadoc.Javadoc:destinationDir}{\texttt{destinationDir}}
\textbar{} \texttt{\$\{project.buildDir\}/docs/javadoc}
\href{https://docs.gradle.org/current/javadoc/org/gradle/external/javadoc/MinimalJavadocOptions.html\#getEncoding()}{\texttt{options.encoding}}
\textbar{} \texttt{"UTF-8"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html\#org.gradle.api.tasks.javadoc.Javadoc:source}{\texttt{source}}
\textbar{} The \texttt{javadoc.source} of all the subprojects.
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html\#org.gradle.api.tasks.javadoc.Javadoc:title}{\texttt{title}}
\textbar{} \texttt{project.reporting.apiDocTitle}
\chapter{Baseline Gradle Plugin}\label{baseline-gradle-plugin}
The Baseline Gradle plugin lets you verify that the OSGi
\href{http://semver.org/}{semantic versioning} rules are obeyed by your
OSGi bundle.
When you run the \hyperref[baseline]{\texttt{baseline}} task, the plugin
\emph{baselines} the new bundle against the latest released non-snapshot
bundle (i.e., the \emph{baseline}). That is, it compares the public
exported API of the new bundle with the baseline. If there are any
changes, it uses the OSGi semantic versioning rules to calculate the
minimum new version. If the new bundle has a lower version, errors are
thrown.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-1}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.baseline", version: "2.1.0"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.baseline"
\end{verbatim}
The Baseline plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
and
\href{https://docs.gradle.org/current/userguide/standard_plugins.html\#sec:base_plugins}{\texttt{reporting-base}}
plugins.
Since the plugin needs to download the baseline, you have to configure a
\href{https://docs.gradle.org/current/userguide/artifact_dependencies_tutorial.html\#sec:repositories_tutorial}{repository}
that hosts it; for example, the central Maven 2 repository:
\begin{verbatim}
repositories {
mavenCentral()
}
\end{verbatim}
\section{Project Extension}\label{project-extension-1}
The Baseline plugin exposes the following properties through the
\texttt{baselineConfiguration} extension:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{allowMavenLocal} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to let the baseline come
from the local Maven cache (by default: \texttt{\$\{user.home\}/.m2}).
If the local Maven cache is not
\href{https://docs.gradle.org/current/userguide/dependency_management.html\#sub:maven_local}{configured}
as a project repository, this property has no effect.
\texttt{lowestBaselineVersion} \textbar{} \texttt{String} \textbar{}
\texttt{"1.0.0"} \textbar{} The greatest project version to ignore for
the baseline check. If the
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html\#org.gradle.api.tasks.bundling.Jar:version}{project
version} is less than or equal to the value of this property, the
\hyperref[baseline]{\texttt{baseline}} task is skipped.
\texttt{lowestMajorVersion} \textbar{} \texttt{Integer} \textbar{}
Content of the file
\texttt{\$\{project.projectDir\}/.lfrbuild-lowest-major-version}, where
the default file name can be changed by setting the project property
\texttt{baseline.lowest.major.version.file}. \textbar{} The lowest major
version of the released artifact to use in the baseline check.
\texttt{lowestMajorVersionRequired} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to fail the build if the
\hyperref[lowestmajorversion]{\texttt{lowestMajorVersion}} is not
specified.
If the \texttt{lowestMajorVersion} is not specified, the plugin runs the
check using the most recent released non-snapshot bundle as baseline,
which matches the
\href{http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html}{version
range} \texttt{(,\$\{project.version\})}. Otherwise, if the
\texttt{lowestMajorVersion} is equal to a value \texttt{L} and the
project has version \texttt{M.x.y} (with \texttt{L} less or equal than
\texttt{M}), multiple checks are performed in order, using the following
version ranges as baseline:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
\texttt{{[}L.0.0,\ (L\ +\ 1).0.0)}
\item
\texttt{{[}(L\ +\ 1).0.0,\ (L\ +\ 2).0.0)}
\item
\ldots{}
\item
\texttt{{[}(M\ -\ 2).0.0,\ (M\ -\ 1).0.0)}
\item
\texttt{{[}(M\ -\ 1).0.0,\ M.0.0)}
\item
\texttt{{[}M.0.0,\ M.x.y)}
\end{enumerate}
The first failing check fails the whole build.
\section{Tasks}\label{tasks-1}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{baseline} \textbar{}
\href{(https://docs.gradle.org/current/userguide/java_plugin.html\#sec:jar)}{\texttt{jar}}
\textbar{} \hyperref[baselinetask]{\texttt{BaselineTask}} \textbar{}
Compares the public API of this project with the public API of the
previous released version, if found.
The \texttt{baseline} task is automatically configured with sensible
defaults:
Property Name \textbar{} Default Value
\hyperref[baselineconfiguration]{\texttt{baselineConfiguration}}
\textbar{}
\hyperref[baseline-dependency]{\texttt{configurations.baseline}}
\hyperref[bndfile]{\texttt{bndFile}} \textbar{}
\texttt{\$\{project.projectDir\}/bnd.bnd}
\hyperref[newjarfile]{\texttt{newJarFile}} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html\#org.gradle.api.tasks.bundling.Jar:archivePath}{\texttt{project.tasks.jar.archivePath}}
\hyperref[sourcedir]{\texttt{sourceDir}} \textbar{} The first
\texttt{resources} directory of the \texttt{main} source set (by
default: \texttt{src/main/resources}).
\section{BaselineTask}\label{baselinetask}
\subsection{Task Properties}\label{task-properties}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{baselineConfiguration} \textbar{}
\texttt{Configuration} \textbar{} \texttt{null} \textbar{} The
configuration that contains exactly one dependency to the baseline
bundle. \texttt{bndFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The BND file of the project. If provided, the
task will automatically update the
\href{http://bnd.bndtools.org/heads/bundle_version.html}{\texttt{Bundle-Version}}
header. \texttt{forceCalculatedVersion} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to fail the baseline check
if the \texttt{Bundle-Version} has been excessively increased.
\texttt{ignoreExcessiveVersionIncreases} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to ignore excessive package
version increase warnings. \texttt{ignoreFailures} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether the build
should not break when semantic versioning errors are found.
\texttt{logFile} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} The file to which the results of the baseline check are
written. \emph{(Read-only)} \texttt{logFileName} \textbar{}
\texttt{String} \textbar{} \texttt{"baseline/\$\{task.name\}.log"}
\textbar{} The name of the file to which the results of the baseline
check are written. If the \texttt{reporting-base} plugin is applied, the
file name is relative to
\href{https://docs.gradle.org/current/dsl/org.gradle.api.reporting.ReportingExtension.html\#org.gradle.api.reporting.ReportingExtension:baseDir}{\texttt{reporting.baseDir}};
otherwise, it's relative to the project directory. \texttt{newJarFile}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The file of
the new OSGi bundle. \texttt{reportDiff} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} if the project property
\texttt{baseline.jar.report.level} has either value \texttt{"diff"} or
\texttt{"persist"}; \texttt{false} otherwise \textbar{} Whether to show
a granular, differential report of all changes that occurred in the
exported packages of the OSGi bundle. \texttt{reportOnlyDirtyPackages}
\textbar{} \texttt{boolean} \textbar{} Value of the project property
\texttt{baseline.jar.report.only.dirty.packages} if specified;
\texttt{true} otherwise. \textbar{} Whether to show only packages with
API changes in the report. \texttt{sourceDir} \textbar{} \texttt{File}
\textbar{} \texttt{null} \textbar{} The directory to which the
\href{http://bnd.bndtools.org/chapters/170-versioning.html\#versioning-packages}{\texttt{packageinfo}}
files are generated or updated.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\section{Helper Tasks}\label{helper-tasks}
If the \hyperref[lowestmajorversion]{\texttt{lowestMajorVersion}}
property is specified with a value \texttt{L}, the plugin creates a
series of helper tasks of type
\hyperref[baselinetask]{\texttt{BaselineTask}} at the end of the
\href{https://docs.gradle.org/current/userguide/build_lifecycle.html\#N11BAE}{project
evaluation}, one for each major version between \texttt{L} and the major
version \texttt{M} of the project:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Task \texttt{baseline\$\{L\ +\ 1\}}, which depends on
\texttt{baseline\$\{L\ +\ 2\}} and uses the version range
\texttt{{[}(L\ +\ 1).0.0,\ (L\ +\ 2).0.0)} as baseline.
\item
Task \texttt{baseline\$\{L\ +\ 2\}}, which depends on
\texttt{baseline\$\{L\ +\ 3\}} and uses the version range
\texttt{{[}(L\ +\ 2).0.0,\ (L\ +\ 3).0.0)} as baseline.
\item
\ldots{}
\item
Task \texttt{baseline\$\{M\ -\ 2\}}, which depends on
\texttt{baseline\$\{M\ -\ 1\}} and uses the version range
\texttt{{[}(M\ -\ 2).0.0,\ (M\ -\ 1).0.0)} as baseline.
\item
Task \texttt{baseline\$\{M\ -\ 1\}}, which depends on
\texttt{baseline\$\{M\}} and uses the version range
\texttt{{[}(M\ -\ 1).0.0,\ M.0.0)} as baseline.
\item
Task \texttt{baseline\$\{M\}}, which uses the version range
\texttt{{[}M.0.0,\ M.x.y)} as baseline.
\end{enumerate}
The \texttt{baseline} task is also configured to use the version range
\texttt{{[}L.0.0,\ (L\ +\ 1).0.0)} as baseline, and to depend on the
task \texttt{baseline\$\{L\ +\ 1\}}. This means that running the
\texttt{baseline} task runs the baseline check against multiple
versions, starting from the most recent \texttt{M} and going back to
\texttt{L}.
Moreover, all tasks except \texttt{baseline\$\{M\}} have the property
\hyperref[ignoreexcessiveversionincreases]{\texttt{ignoreExcessiveVersionIncreases}}
set to \texttt{true}.
\section{Additional Configuration}\label{additional-configuration}
There are additional configurations that can help you baseline your OSGi
bundle.
\section{Baseline Dependency}\label{baseline-dependency}
The plugin creates a configuration called \texttt{baseline} with a
default dependency to a released non-snapshot version of the bundle:
\begin{itemize}
\tightlist
\item
version range \texttt{{[}L.0.0,\ (L\ +\ 1).0.0)} if the
\hyperref[lowestmajorversion]{\texttt{lowestMajorVersion}} property is
specified with a value \texttt{L}.
\item
version range \texttt{(,\$\{project.version\})} otherwise.
\end{itemize}
It is possible to override this setting and use a different version of
the bundle as baseline.
\section{System Properties}\label{system-properties}
It is possible to set the default values of the
\hyperref[ignorefailures]{\texttt{ignoreFailures}} property for a
\texttt{BaselineTask} task via system properties:
\begin{verbatim}
-D${task.name}.ignoreFailures=true
\end{verbatim}
For example, run the following Bash command to execute the baseline
check without breaking the build, in case of errors:
\begin{verbatim}
./gradlew baseline -Dbaseline.ignoreFailures=true
\end{verbatim}
\chapter{Change Log Builder Gradle
Plugin}\label{change-log-builder-gradle-plugin}
The Change Log Builder Gradle plugin lets you generate and maintain a
change log file based on the Git commits in your project. A change log
file generated by this plugin looks like this
\begin{verbatim}
#
# Bundle Version 1.0.1
#
9c77ff4c95cb1a325db3bdd089be105206e8b63c^..b421f00ac84b065685b131833fecc594fc01c760=LPS-123 LPS-1321
#
# Bundle Version 1.0.2
#
b421f00ac84b065685b131833fecc594fc01c760^..bc15d8d84e12b9544f78e4e3743c510dbaec2d89=LPS-456
\end{verbatim}
Every time the \hyperref[buildchangelog]{\texttt{buildChangeLog}} task
is executed, a new line is added to the change log, which lists all Git
\hyperref[ticketidprefixes]{commit prefixes} (usually issue ticket IDs)
that occurred in a certain range. The end of the range is always the tip
of the current branch. The start range can vary, depending on the case:
\begin{itemize}
\tightlist
\item
If \texttt{buildChangeLog} has never been executed for the project,
the change log does not exist. Therefore, the most recent commit from
two years ago is used for the range start.
\item
If a change log already exists for your project, the start range
begins at the range end of the last line in the change log.
\end{itemize}
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-2}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.change.log.builder", version: "1.1.3"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.change.log.builder"
\end{verbatim}
\section{Tasks}\label{tasks-2}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildChangeLog} \textbar{} - \textbar{}
\hyperref[buildchangelogtask]{\texttt{BuildChangeLogTask}} \textbar{}
Builds the change log file for this project.
The \texttt{buildChangeLog} task is automatically configured with
sensible defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied:
Property Name \textbar{} Default Value
\hyperref[changelogheader]{\texttt{changeLogHeader}} \textbar{}
\texttt{"Bundle\ Version\ \$\{project.version\}"}
\hyperref[changelogfile]{\texttt{changeLogFile}} \textbar{}
\textbf{If the \texttt{java} plugin is applied:} The
\texttt{META-INF/liferay-releng.changelog} file in the first
\texttt{resources} directory of the \texttt{main} source set (by
default, \texttt{src/main/resources/META-INF/liferay-releng.changelog}).
\textbf{Otherwise:}
\texttt{"\$\{project.projectDir\}/liferay-releng.changelog"}
\hyperref[dirs]{\texttt{dirs}} \textbar{}
\texttt{{[}project.projectDir{]}}
\section{BuildChangeLogTask}\label{buildchangelogtask}
\subsection{Task Properties}\label{task-properties-1}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{changeLogFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The change log file to build.
\texttt{changeLogHeader} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The header for the new line in the change log.
\texttt{dirs} \textbar{} \texttt{FileCollection} \textbar{}
\texttt{{[}{]}} \textbar{} The directories to consider when listing the
commits in the range specified. \texttt{gitDir} \textbar{} \texttt{File}
\textbar{} \texttt{project.rootDir} \textbar{} The base directory to
start searching for the \texttt{.git} directory. The search proceeds in
all the ancestors of the directory specified. \texttt{rangeEnd}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The hash
of the last commit to consider. If not set, it corresponds to the range
end of the last line in the change log, or the most recent commit from
at least two years ago if the change log file does not exist yet.
\texttt{rangeStart} \textbar{} \texttt{String} \textbar{} \texttt{null}
\textbar{} The hash of the first commit to consider. If not set, it
corresponds to the hash of the tip of the current branch.
\texttt{ticketIdPrefixes} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{}
\texttt{{[}"CLDSVCS",\ "LPS",\ "SOS",\ "SYNC"{]}} \textbar{} The valid
prefix of the Git commit messages to add to the change log. For example,
if a commit message is \texttt{"LPS-123\ Bugfix"}, \texttt{"LPS-123"}
will be added to the change log.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\subsection{Task Methods}\label{task-methods}
Method \textbar{} Description
\texttt{BuildChangeLogTask\ dirs(Iterable\textless{}?\textgreater{}\ dirs)}
\textbar{} Adds directories to consider when listing the commits in the
range specified. \texttt{BuildChangeLogTask\ dirs(Object...\ dirs)}
\textbar{} Adds directories to consider when listing the commits in the
range specified.
\texttt{BuildChangeLogTask\ ticketIdPrefixes(Iterable\textless{}String\textgreater{}\ ticketIdPrefixes)}
\textbar{} Adds valid prefixes of the Git commit messages to add to the
change log.
\texttt{BuildChangeLogTask\ ticketIdPrefixes(String...\ ticketIdPrefixes)}
\textbar{} Adds valid prefixes of the Git commit messages to add to the
change log.
\chapter{CSS Builder Gradle Plugin}\label{css-builder-gradle-plugin}
The CSS Builder Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/css-builder}{Liferay
CSS Builder} tool to compile \href{http://sass-lang.com/}{Sass} files in
your project.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-3}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.css.builder", version: "3.0.0"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.css.builder"
\end{verbatim}
Since the plugin automatically resolves the Liferay CSS Builder library
as a dependency, you have to configure a repository that hosts the
library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-3}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildCSS} \textbar{} - \textbar{}
\hyperref[buildcsstask]{\texttt{BuildCSSTask}} \textbar{} Compiles the
Sass files in this project.
The plugin also adds the following dependencies to tasks defined by the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin:
Name \textbar{} Depends On \texttt{processResources} \textbar{}
\texttt{buildCSS}
The \texttt{buildCSS} task is automatically configured with sensible
defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
or the
\href{https://docs.gradle.org/current/userguide/war_plugin.html}{\texttt{war}}
plugins are applied:
Property Name \textbar{} Default Value
\hyperref[basedir]{\texttt{baseDir}} \textbar{}
\textbf{If the \texttt{java} plugin is applied:} The first
\texttt{resources} directory of the \texttt{main} source set (by
default: \texttt{src/main/resources}).
\textbf{If the \texttt{war} plugin is applied:}
\texttt{project.webAppDir}.
\textbf{Otherwise:} \texttt{null}
\section{BuildCSSTask}\label{buildcsstask}
Tasks of type \texttt{BuildCSSTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.css.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} CSS Builder command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-css-builder-dependency]{\texttt{project.configurations.cssBuilder}}
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/JavaExec.html\#setDefaultCharacterEncoding(java.lang.String)}{\texttt{defaultCharacterEncoding}}
\textbar{} \texttt{"UTF-8"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.css.builder.CSSBuilder"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:systemProperties}{\texttt{systemProperties}}
\textbar{} \texttt{{[}"sass.compiler.jni.clean.temp.dir",\ true{]}}
\subsection{Task Properties}\label{task-properties-2}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{appendCssImportTimestamps} \textbar{}
\texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether to append
the current timestamp to the URLs in the \texttt{@import} CSS at-rules.
It sets the \texttt{sass.append.css.import.timestamps} argument.
\texttt{baseDir} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} The base directory that contains the SCSS files to compile.
It sets the \texttt{sass.docroot.dir} argument. \texttt{cssFiles}
\textbar{} \texttt{FileCollection} \textbar{} - \textbar{} The SCSS
files to compile. \emph{(Read-only)} \texttt{dirNames} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{}
\texttt{{[}"/"{]}} \textbar{} The name of the directories, relative to
\hyperref[basedir]{\texttt{baseDir}}, which contain the SCSS files to
compile. All sub-directories are searched for SCSS files as well. It
sets the \texttt{sass.dir} argument. \texttt{generateSourceMap}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to generate
\href{https://developers.google.com/web/tools/chrome-devtools/debug/readability/source-maps}{source
maps} for easier debugging. It sets the
\texttt{sass.generate.source.map} argument. \texttt{importDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
\texttt{META-INF/resources} directory of the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-css/frontend-css-common}{Liferay
Frontend Common CSS} artifact. This is required in order to make
\href{http://bourbon.io}{Bourbon} and other CSS libraries available to
the compilation. \texttt{importFile} \textbar{} \texttt{File} \textbar{}
\hyperref[liferay-frontend-common-css-dependency]{\texttt{configurations.portalCommonCSS.singleFile}}
\textbar{} The Liferay Frontend Common CSS JAR file. If
\hyperref[importdir]{\texttt{importDir}} is set, this property has no
effect. \texttt{importPath} \textbar{} \texttt{File} \textbar{} -
\textbar{} The value of the \texttt{importDir} property if set;
otherwise \texttt{importFile}. It sets the
\texttt{sass.portal.common.path} argument. \emph{(Read-only)}
\texttt{outputDirName} \textbar{} \texttt{String} \textbar{}
\texttt{".sass-cache/"} \textbar{} The name of the sub-directories where
the SCSS files are compiled to. For each directory that contains SCSS
files, a sub-directory with this name is created. It sets the
\texttt{sass.output.dir} argument. \texttt{outputDirs} \textbar{}
\texttt{FileCollection} \textbar{} - \textbar{} The directories where
the SCSS files are compiled to. Usually, these directories are ignored
by the Version Control System. \emph{(Read-only)} \texttt{precision}
\textbar{} \texttt{int} \textbar{} \texttt{5} \textbar{} The numeric
precision of numbers in Sass. It sets the \texttt{sass.precision}
argument. \texttt{rtlExcludedPathRegexps} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The SCSS file patterns to exclude when converting for
right-to-left (RTL) support. It sets the
\texttt{sass.rtl.excluded.path.regexps} argument.
\texttt{sassCompilerClassName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The type of Sass compiler to use. Supported
values are \texttt{"jni"} and \texttt{"ruby"}. If not set, defaults to
\texttt{"jni"}. It sets the \texttt{sass.compiler.class.name} argument.
\noindent\hrulefill
\textbf{Note:} Liferay's CSS Builder is supported for Oracle's JDK and
uses a native compiler for increased speed. If you're using an IBM JDK,
you may experience issues when building your Sass files (e.g., when
building a theme). It's recommended to switch to using the Oracle JDK,
but if you prefer using the IBM JDK, you must use the fallback Ruby
compiler. You can do this two ways:
\begin{itemize}
\tightlist
\item
If you're working in a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} or using the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins}{Liferay
Gradle Plugins} plugin, set \texttt{sass.compiler.class.name=ruby} in
your \texttt{gradle.properties} file.
\item
Otherwise, set
\texttt{buildCSS.sassCompilerClassName=\textquotesingle{}ruby\textquotesingle{}}
in the project's \texttt{build.gradle} file.
\end{itemize}
The \texttt{sass.compiler.class.name=ruby} Gradle property only works
for modules, so if you're using the Ruby compiler in a WAR project
(e.g., theme), you must use the second option.
Be aware that the Ruby-based compiler doesn't perform as well as the
native compiler, so expect longer compile times.
\noindent\hrulefill
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{int} and \texttt{String} properties, to defer evaluation until
task execution.
\subsection{Task Methods}\label{task-methods-1}
Method \textbar{} Description
\texttt{BuildCSSTask\ dirNames(Iterable\textless{}Object\textgreater{}\ dirNames)}
\textbar{} Adds sub-directory names, relative to
\hyperref[basedir]{\texttt{baseDir}}, which contain the SCSS files to
compile. \texttt{BuildCSSTask\ dirNames(Object...\ dirNames)} \textbar{}
Adds sub-directory names, relative to
\hyperref[basedir]{\texttt{baseDir}}, which contain the SCSS files to
compile.
\texttt{BuildCSSTask\ rtlExcludedPathRegexps(Iterable\textless{}Object\textgreater{}\ rtlExcludedPathRegexps)}
\textbar{} Adds SCSS file patterns to exclude when converting for
right-to-left (RTL) support.
\texttt{BuildCSSTask\ rtlExcludedPathRegexps(Object...\ rtlExcludedPathRegexps)}
\textbar{} Adds SCSS file patterns to exclude when converting for
right-to-left (RTL) support.
\section{Additional Configuration}\label{additional-configuration-1}
There are additional configurations that can help you use the CSS
Builder.
\section{Liferay CSS Builder
Dependency}\label{liferay-css-builder-dependency}
By default, the plugin creates a configuration called
\texttt{cssBuilder} and adds a dependency to the latest released version
of the Liferay CSS Builder. It is possible to override this setting and
use a specific version of the tool by manually adding a dependency to
the \texttt{cssBuilder} configuration:
\begin{verbatim}
dependencies {
cssBuilder group: "com.liferay", name: "com.liferay.css.builder", version: "3.0.0"
}
\end{verbatim}
\section{Liferay Frontend Common CSS
Dependency}\label{liferay-frontend-common-css-dependency}
By default, the plugin creates a configuration called
\texttt{portalCommonCSS} and adds a dependency to the latest released
version of the Liferay Frontend Common CSS artifact. It is possible to
override this setting and use a specific version of the artifact by
manually adding a dependency to the \texttt{portalCommonCSS}
configuration:
\begin{verbatim}
dependencies {
portalCommonCSS group: "com.liferay", name: "com.liferay.frontend.css.common", version: "2.0.1"
}
\end{verbatim}
\chapter{DB Support Gradle Plugin}\label{db-support-gradle-plugin}
The DB Support Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-db-support}{Liferay
DB Support} tool to execute certain actions on a local Liferay database.
So far, the following actions are available:
\begin{itemize}
\tightlist
\item
Cleans the Liferay database from the Service Builder tables and rows
of a module.
\end{itemize}
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-4}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.db.support", version: "1.0.5"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.portal.tools.db.support"
\end{verbatim}
Since the plugin automatically resolves the Liferay DB Support library
as a dependency, you have to configure a repository that hosts the
library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-4}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{cleanServiceBuilder} \textbar{} - \textbar{}
\hyperref[cleanservicebuildertask]{\texttt{CleanServiceBuilderTask}}
\textbar{} Cleans the Liferay database from the Service Builder tables
and rows of a module.
The \texttt{cleanServiceBuilder} task is automatically configured with
sensible defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/standard_plugins.html\#N135C1}{\texttt{base}}
plugin is applied:
Property Name \textbar{} Default Value
\hyperref[servletcontextname]{\texttt{servletContextName}} \textbar{}
\textbf{If the \texttt{base} plugin is applied:} The bundle symbolic
name of the project inferred via the
\href{https://github.com/gradle/gradle/blob/master/subprojects/osgi/src/main/java/org/gradle/api/internal/plugins/osgi/OsgiHelper.java}{\texttt{OsgiHelper}}
class.
\textbf{Otherwise:} \texttt{null}
\hyperref[servicexmlfile]{\texttt{serviceXmlFile}} \textbar{}
\texttt{"\$\{project.projectDir\}/service.xml"}
\section{CleanServiceBuilderTask}\label{cleanservicebuildertask}
Tasks of type \texttt{BuildDeploymentHelperTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} The DB Support command line arguments.
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[jdbc-drivers-dependency]{\texttt{project.configurations.dbSupport}}
+
\hyperref[liferay-db-support-dependency]{\texttt{project.configurations.dbSupportTool}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.portal.tools.db.support.DBSupport"}
\subsection{Task Properties}\label{task-properties-3}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{password} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The user password for connecting to the Liferay
database. It sets the \texttt{-\/-password} argument. If
\hyperref[propertiesfile]{\texttt{propertiesFile}} is set, this property
has no effect. \texttt{propertiesFile} \textbar{} \texttt{File}
\textbar{} \texttt{null} \textbar{} The \texttt{portal-ext.properties}
file that contains the JDBC settings for connecting to the Liferay
database. It sets the \texttt{-\/-properties-file} argument.
\texttt{servletContextName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The servlet context name (usually the value of
the \texttt{Bundle-Symbolic-Name} manifest header) of the module. It
sets the \texttt{-\/-servlet-context-name} argument.
\texttt{serviceXmlFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The \texttt{service.xml} file of the module. It
sets the \texttt{-\/-service-xml-file} argument. \texttt{url} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The JDBC URL for
connecting to the Liferay database. It sets the \texttt{-\/-url}
argument. If \hyperref[propertiesfile]{\texttt{propertiesFile}} is set,
this property has no effect. \texttt{userName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The user name for
connecting to the Liferay database. It sets the \texttt{-\/-user-name}
argument. If \hyperref[propertiesfile]{\texttt{propertiesFile}} is set,
this property has no effect.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{int} and \texttt{String} properties to defer evaluation until
task execution.
\section{Additional Configuration}\label{additional-configuration-2}
There are additional configurations that can help you use the Deployment
Helper.
\section{JDBC Drivers Dependency}\label{jdbc-drivers-dependency}
The plugin creates a configuration called \texttt{dbSupport}, which can
be used to provide the suitable JDBC driver for your Liferay database:
\begin{verbatim}
dependencies {
dbSupport group: "mysql", name: "mysql-connector-java", version: "5.1.23"
dbSupport group: "org.mariadb.jdbc", name: "mariadb-java-client", version: "1.1.9"
dbSupport group: "org.postgresql", name: "postgresql", version: "9.4-1201-jdbc41"
}
\end{verbatim}
\section{Liferay DB Support
Dependency}\label{liferay-db-support-dependency}
By default, the plugin creates a configuration called
\texttt{dbSupportTool} and adds a dependency to the latest released
version of the Liferay DB Support. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{dbSupportTool} configuration:
\begin{verbatim}
dependencies {
dbSupportTool group: "com.liferay", name: "com.liferay.portal.tools.db.support", version: "1.0.8"
}
\end{verbatim}
\chapter{Dependency Checker Gradle
Plugin}\label{dependency-checker-gradle-plugin}
The Dependency Checker Gradle plugin lets you warn users if a specific
configuration dependency is not the latest one available from the Maven
central repository. The plugin eventually fails the build if the
dependency age (the difference between the timestamp of the current
version and the latest version) is above a predetermined threshold.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-5}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.dependency.checker", version: "1.0.3"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.dependency.checker"
\end{verbatim}
\section{Project Extension}\label{project-extension-2}
The Dependency Checker Gradle plugin exposes the following properties
through the extension named \texttt{dependencyChecker}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{ignoreFailures} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} \textbar{} Whether to print an error message
instead of failing the build when the dependency check fails, either for
a network error or because the dependency is out-of-date.
The same extension exposes the following methods:
Method \textbar{} Description
\texttt{void\ maxAge(Map\textless{}?,\ ?\textgreater{}\ args)}
\textbar{} Declares the max age allowed for a dependency. The
\texttt{args} map must contain the following entries:
\texttt{configuration}: the configuration name
\texttt{group}: the dependency group
\texttt{name}: the dependency name
\texttt{maxAge}: an instance of
\href{http://docs.groovy-lang.org/latest/html/api/groovy/time/Duration.html}{\texttt{groovy.time.Duration}}
that represents the maximum age allowed for the dependency
\texttt{throwError}: a \texttt{boolean} value representing whether to
throw an error if the dependency is out-of-date
\section{Additional Configuration}\label{additional-configuration-3}
There are additional configurations that can help you use the Deployment
Helper.
\section{Project Properties}\label{project-properties}
It is possible to set the default values of the
\hyperref[ignorefailures]{\texttt{ignoreFailures}} property via the
project property \texttt{dependencyCheckerIgnoreFailures}:
\begin{verbatim}
-PdependencyCheckerIgnoreFailures=false
\end{verbatim}
\chapter{Deployment Helper Gradle
Plugin}\label{deployment-helper-gradle-plugin}
The Deployment Helper Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/deployment-helper}{Liferay
Deployment Helper} tool to create a cluster deployable WAR from your
OSGi artifacts.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-6}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.deployment.helper", version: "1.0.5"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.deployment.helper"
\end{verbatim}
Since the plugin automatically resolves the Liferay Deployment Helper
library as a dependency, you have to configure a repository that hosts
the library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-5}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildDeploymentHelper} \textbar{} - \textbar{}
\hyperref[builddeploymenthelpertask]{\texttt{BuildDeploymentHelperTask}}
\textbar{} Builds a WAR which contains one or more files that are copied
once the WAR is deployed.
\section{BuildDeploymentHelperTask}\label{builddeploymenthelpertask}
Tasks of type \texttt{BuildDeploymentHelperTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} The Deployment Helper command line arguments.
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-deployment-helper-dependency]{\texttt{project.configurations.deploymentHelper}}
\hyperref[deploymentfiles]{\texttt{deploymentFiles}} \textbar{} The
output files of the
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#sec:jar}{\texttt{jar}}
tasks of this project and all its sub-projects.
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.deployment.helper.DeploymentHelper"}
\hyperref[outputfile]{\texttt{outputFile}} \textbar{}
\texttt{"\$\{project.buildDir\}/\$\{project.name\}.war"}
\subsection{Task Properties}\label{task-properties-4}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{deploymentFiles} \textbar{} \texttt{FileCollection}
\textbar{} \texttt{{[}{]}} \textbar{} The files or directories to
include in the WAR and copy once the WAR is deployed. If a directory is
added to this collection, all the JAR files contained in the directory
are included in the WAR. \texttt{deploymentPath} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} The directory to which
the included files are copied. \texttt{outputFile} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} The WAR file to build.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
\subsection{Task Methods}\label{task-methods-2}
Method \textbar{} Description
\texttt{BuildDeploymentHelperTask\ deploymentFiles(Iterable\textless{}?\textgreater{}\ deploymentFiles)}
\textbar{} Adds files or directories to include in the WAR and copy once
the WAR is deployed. The values are evaluated as per
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:files(java.lang.Object\%5B\%5D)}{\texttt{project.files}}.
\texttt{BuildDeploymentHelperTask\ deploymentFiles(Object...\ deploymentFiles)}
\textbar{} Adds files or directories to include in the WAR and copy once
the WAR is deployed. The values are evaluated as per
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:files(java.lang.Object\%5B\%5D)}{\texttt{project.files}}.
\section{Additional Configuration}\label{additional-configuration-4}
There are additional configurations that can help you use the Deployment
Helper.
\section{Liferay Deployment Helper
Dependency}\label{liferay-deployment-helper-dependency}
By default, the plugin creates a configuration called
\texttt{deploymentHelper} and adds a dependency to the latest released
version of the Liferay Deployment Helper. It is possible to override
this setting and use a specific version of the tool by manually adding a
dependency to the \texttt{deploymentHelper} configuration:
\begin{verbatim}
dependencies {
deploymentHelper group: "com.liferay", name: "com.liferay.deployment.helper", version: "1.0.4"
}
\end{verbatim}
\chapter{Go Gradle Plugin}\label{go-gradle-plugin}
The Go Gradle plugin lets you run \href{https://golang.org/}{Go} as part
of your build.
The plugin has been successfully tested with Gradle 3.5.1 up to 4.10.2.
\section{Usage}\label{usage-7}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.go", version: "1.0.0"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.go"
\end{verbatim}
\section{Project Extension}\label{project-extension-3}
The Go Gradle plugin exposes the following properties through the
extension named \texttt{go}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{goDir} \textbar{} \texttt{File} \textbar{}
\texttt{"\$\{project.buildDir\}/go"} \textbar{} The directory where the
Go distribution is unpacked. \texttt{goUrl} \textbar{} \texttt{String}
\textbar{}
\texttt{"https://dl.google.com/go/go\$\{go.goVersion\}.\$\{platform\}-\$\{bitMode\}.\$\{extension\}}
\textbar{} The URL of the Go distribution to download.
\texttt{goVersion} \textbar{} \texttt{String} \textbar{}
\texttt{"1.11.4"} \textbar{} The Go distribution's version to use.
\texttt{workingDir} \textbar{} \texttt{File} \textbar{}
\texttt{"\$\{project.projectDir\}"} \textbar{} The directory that
contains the project's Go source code.
\section{Tasks}\label{tasks-6}
The plugin adds a series of tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{downloadGo} \textbar{} - \textbar{}
\hyperref[downloadgotask]{\texttt{DownloadGoTask}} \textbar{} Downloads
and unpacks the local Go distribution for the project.
\hyperref[gocommandprogramname-task]{\texttt{goBuild\$\{programName\}}}
\textbar{} \texttt{downloadGo} \textbar{}
\hyperref[executegotask]{\texttt{ExecuteGoTask}} \textbar{} Compiles
packages and dependencies for the Go program.
\hyperref[gocommandprogramname-task]{\texttt{goClean\$\{programName\}}}
\textbar{} \texttt{downloadGo} \textbar{}
\hyperref[executegotask]{\texttt{ExecuteGoTask}} \textbar{} Removes
object files for the Go program.
\hyperref[gocommandprogramname-task]{\texttt{goRun\$\{programName\}}}
\textbar{} \texttt{downloadGo} \textbar{}
\hyperref[executegotask]{\texttt{ExecuteGoTask}} \textbar{} Compiles and
runs the Go program.
\hyperref[gocommandprogramname-task]{\texttt{goTest\$\{programName\}}}
\textbar{} \texttt{downloadGo} \textbar{}
\hyperref[executegotask]{\texttt{ExecuteGoTask}} \textbar{} Tests
packages for the Go program.
\section{DownloadGoTask}\label{downloadgotask}
The purpose of this task is to download and unpack a Go distribution.
\subsection{Task Properties}\label{task-properties-5}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{goDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory where the Go distribution is
unpacked. \texttt{goUrl} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The URL of the Go distribution to download.
The \texttt{File} type support any type that can be resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{ExecuteGoTask}\label{executegotask}
This is the base task to run Go in a Gradle build. All tasks of type
\texttt{ExecuteGoTask} automatically depend on
\hyperref[downloadgo]{\texttt{downloadGo}}.
\subsection{Task Properties}\label{task-properties-6}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{args} \textbar{}
\texttt{List\textless{}Object\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The arguments for the Go invocation. \texttt{command}
\textbar{} \texttt{String} \textbar{} \texttt{"go"} \textbar{} The file
name of the executable to invoke. \texttt{environment} \textbar{}
\texttt{Map\textless{}Object,\ Object\textgreater{}} \textbar{}
\texttt{{[}{]}} \textbar{} The environment variables for the Go
invocation. \texttt{inheritProxy} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to set the \texttt{http\_proxy},
\texttt{https\_proxy}, and \texttt{no\_proxy} environment variables in
the Go invocation based on the values of the system properties
\texttt{https.proxyHost}, \texttt{https.proxyPort},
\texttt{https.proxyUser}, \texttt{https.proxyPassword},
\texttt{https.nonProxyHosts}, \texttt{https.proxyHost},
\texttt{https.proxyPort}, \texttt{https.proxyUser},
\texttt{https.proxyPassword}, and \texttt{https.nonProxyHosts}. If these
environment variables are already set, their values will not be
overwritten. \texttt{goDir} \textbar{} \texttt{File} \textbar{}
\texttt{go.goDir}{]}(\#godir) \textbar{} The directory that contains the
executable to invoke. \texttt{useGradleExec} \textbar{} \texttt{boolean}
\textbar{}
\textbf{If running in a
\href{https://docs.gradle.org/current/userguide/gradle_daemon.html}{Gradle
Daemon}:} \texttt{true}
\textbf{Otherwise:} \texttt{false}
\textbar{} Whether to invoke Go using
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:exec(org.gradle.api.Action)}{\texttt{project.exec}},
which can solve hanging problems with the Gradle Daemon.
\texttt{workingDir} \textbar{} \texttt{File} \textbar{}
\texttt{go.workingDir}{]}(\#workingdir) \textbar{} The working directory
to use in the Go invocation.
The type \texttt{File} properties support any type that can be resolved
by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\subsection{Task Methods}\label{task-methods-3}
Method \textbar{} Description
\texttt{ExecuteGoTask\ args(Iterable\textless{}?\textgreater{}\ args)}
\textbar{} Adds arguments for the Go invocation.
\texttt{ExecuteGoTask\ args(Object...\ args)} \textbar{} Adds arguments
for the Go invocation.
\texttt{ExecuteGoTask\ environment(Map\textless{}?,\ ?\textgreater{}\ environment)}
\textbar{} Adds environment variables for the Go invocation.
\texttt{ExecuteGoTask\ environment(Object\ key,\ Object\ value)}
\textbar{} Adds an environment variable for the Go invocation.
\section{\texorpdfstring{go\({command}\)\{programName\}
Task}{go\{command\}\{programName\} Task}}\label{gocommandprogramname-task}
For each Go program in
\hyperref[workingdirproperty]{\texttt{workingDir}}, four tasks of type
\hyperref[executegotask]{\texttt{ExecuteGoTask}} are added. Each of
these tasks are automatically configured with sensible defaults:
Property Name \textbar{} Default Value \texttt{args} \textbar{}
\texttt{{[}"\$\{command\}",\ "\$\{programFile.absolutePath\}"{]}}
\chapter{Gulp Gradle Plugin}\label{gulp-gradle-plugin}
The Gulp Gradle plugin lets you run \href{http://gulpjs.com/}{Gulp}
tasks as part of your build.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-8}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.gulp", version: "2.0.59"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.gulp"
\end{verbatim}
The Gulp plugin automatically applies the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-node}{\texttt{com.liferay.node}}
plugin.
\section{Tasks}\label{tasks-7}
The plugin adds one
\href{https://docs.gradle.org/current/userguide/more_about_tasks.html\#sec:task_rules}{task
rule} to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{gulp\textless{}Task\textgreater{}} \textbar{}
\texttt{downloadNode}, \texttt{npmInstall} \textbar{}
\hyperref[executegulptask]{\texttt{ExecuteGulpTask}} \textbar{} Executes
a named Gulp task.
\section{ExecuteGulpTask}\label{executegulptask}
Tasks of type \texttt{ExecuteGulpTask} extend
\href{/docs/7-2/reference/-/knowledge_base/r/node-gradle-plugin\#executenodescripttask}{\texttt{ExecuteNodeScriptTask}},
so all its properties and methods, such as \texttt{args} and
\texttt{inheritProxy}, are available. They also have the following
properties set by default:
Property Name \textbar{} Default Value \texttt{scriptFile} \textbar{}
\texttt{"node\_modules/gulp/bin/gulp.js"}
Gulp must be already installed in the \texttt{node\_modules} directory
of the project; otherwise, it will not be downloaded by the task. In
order to ensure Gulp is installed, you can add the Gulp dependency to
the project's \texttt{package.json} file.
\subsection{Task Properties}\label{task-properties-7}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{gulpCommand} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The Gulp task to execute.
It is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\chapter{Jasper JSPC Gradle Plugin}\label{jasper-jspc-gradle-plugin}
The Jasper JSPC Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/jasper-jspc}{Liferay
Jasper JSPC} tool to compile the JavaServer Pages (JSP) files in your
project. This can be useful to
\begin{itemize}
\tightlist
\item
check for errors in the JSP files.
\item
pre-compile the JSP files for better performance.
\end{itemize}
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-9}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.jasper.jspc", version: "2.0.5"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.jasper.jspc"
\end{verbatim}
The Jasper JSPC plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin.
Since the plugin automatically resolves the Liferay Jasper JSPC library
as a dependency, you have to configure a repository that hosts the
library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-8}
The plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{compileJSP} \textbar{} \texttt{generateJSPJava} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html}{\texttt{JavaCompile}}
\textbar{} Compiles JSP files to check for errors.
\texttt{generateJSPJava} \textbar{}
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#sec:jar}{\texttt{jar}}
\textbar{} \hyperref[compilejsptask]{\texttt{CompileJSPTask}} \textbar{}
Compiles JSP files to Java source files to check for errors.
The \texttt{generateJSPJava} task is automatically configured with
sensible defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/war_plugin.html}{\texttt{war}}
plugin is applied:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-jasper-jspc-dependency]{\texttt{project.configurations.jspCTool}}
\hyperref[destinationdir]{\texttt{destinationDir}} \textbar{}
\texttt{"\$\{project.buildDir\}/jspc"}
\hyperref[jspcclasspath]{\texttt{jspCClasspath}} \textbar{}
\hyperref[jsp-compilation-classpath]{\texttt{project.configurations.jspC}}
\hyperref[webappdir]{\texttt{webAppDir}} \textbar{}
\textbf{If the \texttt{war} plugin is applied:}
\texttt{project.webAppDir}.
\textbf{Otherwise:} The first \texttt{resources} directory of the
\texttt{main} source set (by default, \texttt{src/main/resources}).
The \texttt{compileJSP} task is also configured with the following
defaults:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html\#org.gradle.api.tasks.compile.JavaCompile:classpath}{\texttt{classpath}}
\textbar{}
\texttt{project.configurations.jspCTool\ +\ project.configurations.jspC}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html\#org.gradle.api.tasks.compile.JavaCompile:destinationDir}{\texttt{destinationDir}}
\textbar{} \texttt{compileJSP.temporaryDir}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html\#org.gradle.api.tasks.compile.JavaCompile:source}{\texttt{source}}
\textbar{} \texttt{generateJSPJava.outputs}
\section{CompileJSPTask}\label{compilejsptask}
Tasks of type \texttt{CompileJSPTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.css.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.jasper.jspc.JspC"}
\subsection{Task Properties}\label{task-properties-8}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{destinationDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory where the the JSP files are
compiled to. Package directories are automatically generated based on
the directories containing the uncompiled JSP files. It sets the
\texttt{-d} argument. \texttt{jspCClasspath} \textbar{}
\texttt{FileCollection} \textbar{} \texttt{null} \textbar{} The
classpath to use for the JSP files compilation. \texttt{webAppDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
directory containing the web application. All JSP files in the directory
and its subdirectories are compiled. It sets the \texttt{-webapp}
argument.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
\section{Additional Configuration}\label{additional-configuration-5}
There are additional configurations that can help you use Jasper JSPC.
\section{JSP Compilation Classpath}\label{jsp-compilation-classpath}
The plugin creates a configuration called \texttt{jspC} and adds several
dependencies at the end of the configuration phase of the project:
\begin{itemize}
\tightlist
\item
the JAR file of the project generated by the
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#sec:jar}{\texttt{jar}}
task.
\item
the output files of the \texttt{main} source set.
\item
the \texttt{compileClasspath} file collection of the \texttt{main}
source set.
\end{itemize}
If necessary, it is possible to add more dependencies to the
\texttt{jspC} configuration.
\section{Liferay Jasper JSPC
Dependency}\label{liferay-jasper-jspc-dependency}
By default, the plugin creates a configuration called \texttt{jspCTool}
and adds a dependency to the latest released version of the Liferay
Jasper JSPC. It is possible to override this setting and use a specific
version of the tool by manually adding a dependency to the
\texttt{jspCTool} configuration:
\begin{verbatim}
dependencies {
jspCTool group: "com.liferay", name: "com.liferay.jasper.jspc", version: "1.0.11"
jspCTool group: "org.apache.ant", name: "ant", version: "1.9.4"
}
\end{verbatim}
\chapter{Javadoc Formatter Gradle
Plugin}\label{javadoc-formatter-gradle-plugin}
The Javadoc Formatter Gradle plugin lets you format project Javadoc
comments using the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/javadoc-formatter}{Liferay
Javadoc Formatter tool}. The tool lets you generate:
\begin{itemize}
\tightlist
\item
Default
\href{http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html\#@author}{\texttt{@author}}
tags to all classes.
\item
Comment stubs to classes, fields, and methods.
\item
Missing
\href{https://docs.oracle.com/javase/8/docs/api/java/lang/Override.html}{\texttt{@Override}}
annotations.
\item
An XML representation of the Javadoc comments, which can be used by
tools in order to index the Javadocs of the project.
\end{itemize}
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-10}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.javadoc.formatter", version: "1.0.27"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.javadoc.formatter"
\end{verbatim}
Since the plugin automatically resolves the Liferay Javadoc Formatter
library as a dependency, you have to configure a repository that hosts
the library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-9}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{formatJavadoc} \textbar{} - \textbar{}
\hyperref[formatjavadoctask]{\texttt{FormatJavadocTask}} \textbar{} Runs
the Liferay Javadoc Formatter to format files.
\section{FormatJavadocTask}\label{formatjavadoctask}
Tasks of type \texttt{FormatJavadocTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, like
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} Javadoc Formatter command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-javadoc-formatter-dependency]{\texttt{project.configurations.javadocFormatter}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.javadoc.formatter.JavadocFormatter"}
\subsection{Task Properties}\label{task-properties-9}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{author} \textbar{} \texttt{String} \textbar{}
\texttt{"Brian\ Wing\ Shun\ Chan"} \textbar{} The value of the
\texttt{@author} tag to add at class level if missing. It sets the
\texttt{javadoc.author} argument. \texttt{generateXML} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether to
generate a XML representation of the Javadoc comments. The XML files are
generated in the \texttt{src/main/resources} directory only if the Java
files are contained in \texttt{src/main/java}. It sets the
\texttt{javadoc.generate.xml} argument.
\texttt{initializeMissingJavadocs} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to add comment stubs at the
class, field, and method levels. If \texttt{false}, only the class-level
\texttt{@author} is added. It sets the \texttt{javadoc.init} argument.
\texttt{limits} \textbar{} \texttt{List\textless{}String\textgreater{}}
\textbar{} \texttt{{[}{]}} \textbar{} The Java file name patterns,
relative to
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:workingDir}{\texttt{workingDir}},
to include when formatting Javadoc comments. The patterns must be
specified without the \texttt{.java} file type suffix. If empty, all
Java files are formatted. It sets the \texttt{javadoc.limit} argument.
\texttt{lowestSupportedJavaVersion} \textbar{} \texttt{double}
\textbar{} \texttt{1.7} \textbar{} If a method is annotated with the
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/javadoc-formatter/src/main/java/com/liferay/javadoc/formatter/SinceJava.java}{\texttt{@SinceJava}}
annotation and its \texttt{value} argument is greater than the value
specified for the \texttt{lowestSupportedJavaVersion} property, then the
\texttt{@Override} annotation is not automatically added, even if it is
missing. It sets the \texttt{javadoc.lowest.supported.java.version}
argument. See
\href{https://issues.liferay.com/browse/LPS-37353}{LPS-37353}.
\texttt{outputFilePrefix} \textbar{} \texttt{String} \textbar{}
\texttt{"javadocs"} \textbar{} The file name prefix of the XML
representation of the Javadoc comments. If \texttt{generateXML} is
\texttt{false}, this property is not used. It sets the
\texttt{javadoc.output.file.prefix} argument. \texttt{updateJavadocs}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to fix existing comment blocks by adding missing tags. It sets the
\texttt{javadoc.update} argument.
It is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\subsection{Task Methods}\label{task-methods-4}
Method \textbar{} Description
\texttt{FormatJavadocTask\ dirNames(Iterable\textless{}Object\textgreater{}\ limits)}
\textbar{} Adds Java file name patterns, relative to
\texttt{workingDir}, to include when formatting Javadoc comments.
\texttt{FormatJavadocTask\ dirNames(Object...\ limits)} \textbar{} Adds
Java file name patterns, relative to \texttt{workingDir}, to include
when formatting Javadoc comments.
\section{Additional Configuration}\label{additional-configuration-6}
There are additional configurations that can help you use the Javadoc
Formatter.
\section{Liferay Javadoc Formatter
Dependency}\label{liferay-javadoc-formatter-dependency}
By default, the plugin creates a configuration called
\texttt{javadocFormatter} and adds a dependency to the latest released
version of the Liferay Javadoc Formatter. It is possible to override
this setting and use a specific version of the tool by manually adding a
dependency to the \texttt{javadocFormatter} configuration:
\begin{verbatim}
dependencies {
javadocFormatter group: "com.liferay", name: "com.liferay.javadoc.formatter", version: "1.0.32"
}
\end{verbatim}
If the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied, the \texttt{javadocFormatter} configuration
automatically extends from the
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#sec:java_plugin_and_dependency_management}{\texttt{compile}}
configuration.
\section{System Properties}\label{system-properties-1}
It is possible to set the default values of the \texttt{generateXML},
\texttt{initializeMissingJavadocs}, \texttt{limits}, and
\texttt{updateJavadocs} properties for a \texttt{FormatJavadocTask} task
via system properties:
\begin{itemize}
\tightlist
\item
\texttt{-D\$\{task.name\}.generate.xml=true}
\item
\texttt{-D\$\{task.name\}.init=SomeClassName1,SomeClassName2,com.liferay.portal.**}
\item
\texttt{-D\$\{task.name\}.limit=**/com/example/}
\item
\texttt{-D\$\{task.name\}.update=true}
\end{itemize}
\chapter{JS Module Config Generator Gradle
Plugin}\label{js-module-config-generator-gradle-plugin}
The JS Module Config Generator Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-module-config-generator}{Liferay
AMD Module Config Generator} to generate the configuration file needed
to load AMD files via combo loader in Liferay.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-11}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.js.module.config.generator", version: "2.1.57"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.js.module.config.generator"
\end{verbatim}
The JS Module Config Generator plugin automatically applies the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-node}{\texttt{com.liferay.node}}
plugin.
\section{Project Extension}\label{project-extension-4}
The JS Module Config Generator plugin exposes the following properties
through the extension named \texttt{jsModuleConfigGenerator}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{version} \textbar{} \texttt{String} \textbar{}
\texttt{"1.2.1"} \textbar{} The version of the Liferay AMD Module Config
Generator to use.
\section{Tasks}\label{tasks-10}
The plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{configJSModules} \textbar{}
\texttt{downloadLiferayModuleConfigGenerator}, \texttt{processResources}
\textbar{} \hyperref[configjsmodulestask]{\texttt{ConfigJSModulesTask}}
\textbar{} Generates the configuration file needed to load AMD files via
combo loader in Liferay. \texttt{downloadLiferayModuleConfigGenerator}
\textbar{} \texttt{downloadNode} \textbar{}
\texttt{DownloadNodeModuleTask} \textbar{} Downloads the Liferay AMD
Module Config Generator in the project's \texttt{node\_modules}
directory.
By default, the \texttt{downloadLiferayModuleConfigGenerator} task
downloads the version of \texttt{liferay-module-config-generator}
declared in the
\hyperref[version]{\texttt{jsModuleConfigGenerator.version}} property.
If the project's \texttt{package.json} file, however, already lists the
\texttt{liferay-module-config-generator} package in its
\texttt{dependencies} or \texttt{devDependencies}, the
\texttt{downloadLiferayModuleConfigGenerator} task is disabled.
The \texttt{configJSModules} task is automatically configured with
sensible defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied:
Property Name \textbar{} Default Value
\hyperref[moduleconfigfile]{\texttt{moduleConfigFile}} \textbar{}
\texttt{"\$\{project.projectDir\}/package.json"}
\hyperref[outputfile]{\texttt{outputFile}} \textbar{}
\texttt{"\$\{sourceSets.main.output.resourcesDir\}/META-INF/config.json"}
\hyperref[sourcedir]{\texttt{sourceDir}} \textbar{}
\texttt{"\$\{sourceSets.main.output.resourcesDir\}/META-INF/resources"}
The plugin also adds the following dependencies to tasks defined by the
\texttt{java} plugin:
Name \textbar{} Depends On \texttt{classes} \textbar{}
\texttt{configJSModules}
If the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-js-transpiler}{\texttt{com.liferay.js.transpiler}}
plugin is applied, the \texttt{configJSModules} task is configured to
always run after the \texttt{transpileJS} task.
\section{ConfigJSModulesTask}\label{configjsmodulestask}
Tasks of type \texttt{ConfigJSModulesTask} extend
\texttt{ExecuteNodeScriptTask}, so all its properties and methods, such
as \texttt{args}, \texttt{inheritProxy}, and \texttt{workingDir}, are
available. The \texttt{ConfigJSModulesTask} instances also implement the
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/util/PatternFilterable.html}{\texttt{PatternFilterable}}
interface, which lets you specify include and exclude patterns for the
files in \hyperref[sourcedir]{\texttt{sourceDir}} to process.
They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/util/PatternFilterable.html\#getIncludes()}{\texttt{includes}}
\textbar{} \texttt{{[}"**/*.es.js*",\ "**/*.soy.js*"{]}}
\texttt{scriptFile} \textbar{}
\texttt{"\$\{downloadLiferayModuleConfigGenerator.moduleDir\}/bin/index.js"}
The purpose of this task is to run the Liferay AMD Module Config
Generator from the included files in
\hyperref[sourcedir]{\texttt{sourceDir}}. The generator processes these
files and creates a configuration file in the location specified by the
\hyperref[outputfile]{\texttt{outputFile}} property.
\subsection{Task Properties}\label{task-properties-10}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{configVariable} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{}
The~configuration~variable~to~which~the~modules~should~be~added. It sets
the \texttt{-\/-config} argument. \texttt{customDefine} \textbar{}
\texttt{String} \textbar{} \texttt{"Liferay.Loader"} \textbar{} The
namespace of the \texttt{define(...)} call to use in the JS files. It
sets the \texttt{-\/-namespace} argument. \texttt{ignorePath} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether not to
create module \texttt{path} and \texttt{fullPath} properties. It sets
the \texttt{-\/-ignorePath} argument. \texttt{keepFileExtension}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to keep the file extension when generating the module name. It sets the
\texttt{-\/-keepExtension} argument. \texttt{lowerCase} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether to convert
file name to lower case before using it as the module name. It sets the
\texttt{-\/-lowerCase} argument. \texttt{moduleConfigFile} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} The JSON file which
contains configuration data for the modules. It sets the
\texttt{-\/-moduleConfig} argument. \texttt{moduleExtension} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The extension for
the module file (e.g., \texttt{.js}). If specified, use the provided
string~as~an~extension~instead~to~get~it~automatically~from~the~file~name.
It sets the \texttt{-\/-extension} argument. \texttt{moduleFormat}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The
regular expression and value to apply to the file name when generating
the module name. It sets the \texttt{-\/-format} argument.
\texttt{outputFile} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} The file where the generated configuration is stored. It sets
the \texttt{-\/-output} argument. \texttt{sourceDir} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} The directory that
contains the files to process.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{int} and \texttt{String} properties to defer evaluation until
task execution.
\chapter{JS Transpiler Gradle Plugin}\label{js-transpiler-gradle-plugin}
The JS Transpiler Gradle plugin lets you run
\href{https://github.com/metal/metal-cli}{\texttt{metal-cli}} to build
\href{http://metaljs.com/}{Metal.js} code, compile Soy files, and
transpile ES6 to ES5.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-12}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.js.transpiler", version: "2.4.36"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.js.transpiler"
\end{verbatim}
There are two JS Transpiler Gradle plugins you can apply to your
project:
\begin{itemize}
\item
\hyperref[js-transpiler-plugin]{\emph{JS Transpiler Plugin}}: builds
Metal.js code, compiles Soy files, and transpiles ES6 to ES5:
\begin{verbatim}
apply plugin: "com.liferay.js.transpiler"
\end{verbatim}
\item
\hyperref[js-transpiler-base-plugin]{\emph{JS Transpiler Base
Plugin}}: provides a way to use Gradle dependencies (such as an
\href{https://docs.gradle.org/current/userguide/dependency_management.html\#sub:module_dependencies}{external
module} or
\href{https://docs.gradle.org/current/userguide/dependency_management.html\#sub:project_dependencies}{project
dependencies}) in Node.js scripts:
\begin{verbatim}
apply plugin: "com.liferay.js.transpiler.base"
\end{verbatim}
\end{itemize}
\section{JS Transpiler Plugin}\label{js-transpiler-plugin}
The JS Transpiler plugin automatically applies the
\hyperref[js-transpiler-base-plugin]{\emph{JS Transpiler Base Plugin}}.
The plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{downloadMetalCli} \textbar{} \texttt{downloadNode} \textbar{}
\texttt{DownloadNodeModuleTask} \textbar{} Downloads \texttt{metal-cli}
in the project's \texttt{node\_modules} directory. \texttt{transpileJS}
\textbar{} \texttt{downloadMetalCli},
\texttt{expandJSCompileDependencies}, \texttt{npmInstall},
\texttt{processResources} \textbar{}
\hyperref[transpilejstask]{\texttt{TranspileJSTask}} \textbar{} Builds
Metal.js code.
By default, the \texttt{downloadMetalCli} task downloads the version
1.3.1 of \texttt{metal-cli}. If the project's \texttt{package.json}
file, however, already lists the \texttt{metal-cli} package in its
\texttt{dependencies} or \texttt{devDependencies}, the
\texttt{downloadMetalCli} task is disabled.
The \texttt{transpileJS} task is automatically configured with sensible
defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied:
Property Name \textbar{} Default Value
\hyperref[sourcedir]{\texttt{sourceDir}} \textbar{} The directory
\texttt{META-INF/resources} in the first \texttt{resources} directory of
the \texttt{main} source set (by default,
\texttt{src/main/resources/META-INF/resources}). \texttt{workingDir}
\textbar{}
\texttt{"\$\{sourceSets.main.output.resourcesDir\}/META-INF/resources"}
The plugin also adds the following dependencies to tasks defined by the
\texttt{java} plugin:
Name \textbar{} Depends On \texttt{classes} \textbar{}
\texttt{transpileJS}
The plugin adds a new configuration to the project called
\texttt{soyCompile}. If one or more dependencies are added to this
configuration, they will be expanded into temporary directories and
passed to the \texttt{transpileJS} task as additional
\hyperref[soydependencies]{\texttt{soyDependencies}} values.
\section{JS Transpiler Base Plugin}\label{js-transpiler-base-plugin}
The JS Transpiler Base plugin automatically applies the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-node}{\texttt{com.liferay.node}}
plugin.
The plugin adds a new configuration to the project called
\texttt{jsCompile}. If one or more dependencies are added to this
configuration, they will be expanded into sub-directories of the
\texttt{node\_modules} directory, with names equal to the names of the
dependencies.
The plugin also adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{expandJSCompileDependencies} \textbar{} - \textbar{}
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/DefaultTask.html}{\texttt{DefaultTask}}
\textbar{} Expands the additional configured JavaScript dependencies.
The task itself does not do any work, but depends on a series of
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html}{Copy}
tasks called \texttt{expandJSCompileDependency\$\{file\}}, which expand
each dependency declared in the \texttt{jsCompile} configuration into
the \texttt{node\_modules} directory.
All the tasks of type \texttt{ExecuteNpmTask} whose name starts with
\texttt{"npmRun"} are configured to depend on
\texttt{expandJSCompileDependencies}. This means that, before running
any \href{https://docs.npmjs.com/misc/scripts}{script} declared in the
\texttt{package.json} file of the project, all the \texttt{jsCompile}
dependencies will be expanded into the \texttt{node\_modules} directory.
\section{Tasks}\label{tasks-11}
\section{TranspileJSTask}\label{transpilejstask}
Tasks of type \texttt{TranspileJSTask} extend
\texttt{ExecuteNodeScriptTask}, so all its properties and methods, such
as \texttt{args}, \texttt{inheritProxy}, and \texttt{workingDir}, are
available. They also have the following properties set by default:
Property Name \textbar{} Default Value \texttt{scriptFile} \textbar{}
\texttt{"\$\{downloadMetalCli.moduleDir\}/index.js"}
\texttt{soySrcIncludes} \textbar{} \texttt{{[}"**/*.soy"{]}}
\texttt{srcIncludes} \textbar{}
\texttt{{[}"**/*.es.js*",\ "**/*.soy.js*"{]}}
The purpose of this task is to run the \texttt{build} command of
\texttt{metal-cli} to build Metal.js code from
\hyperref[sourcedir]{\texttt{sourceDir}} into the \texttt{workingDir}
directory.
\subsection{Task Properties}\label{task-properties-11}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{bundleFileName} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The name of the final bundle file
for formats (e.g., \emph{globals}) that create one. It sets the
\texttt{-\/-bundleFileName} argument. \texttt{globalName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The name of the
global variable that holds exported modules. It sets the
\texttt{-\/-globalName} argument. This is only used by the
\emph{globals} format build. \texttt{moduleName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The name of the
project that is being compiled. All built modules are stored in a folder
with this name. It sets the \texttt{-\/-moduleName} argument. This is
only used by the \emph{amd} format build. \texttt{modules} \textbar{}
\texttt{String} \textbar{} \texttt{"amd"} \textbar{} The format(s) that
the source files are built to. It sets the \texttt{-\/-format} argument.
\texttt{skipWhenEmpty} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to disable the task and remove its
dependencies if the \hyperref[sourcefiles]{\texttt{sourceFiles}}
property does not return any file at the end of the project evaluation.
\texttt{sourceDir} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} The directory that contains the files to build.
\texttt{sourceFiles} \textbar{} \texttt{FileCollection} \textbar{}
\texttt{{[}{]}} \textbar{} The Soy and JS files to compile.
\emph{(Read-only)} \texttt{sourceMaps} \textbar{} \texttt{SourceMaps}
\textbar{} \texttt{enabled} \textbar{} Whether to generate source map
files. Available values include \texttt{disabled}, \texttt{enabled}, and
\texttt{enabled\_inline}. \texttt{soyDependencies} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{}
\texttt{{[}"\$\{npmInstall.workingDir\}/node\_modules/clay*/src/**/*.soy",\ "\$\{npmInstall.workingDir\}/node\_modules/metal*/src/**/*.soy"{]}}
\textbar{} The path GLOBs of Soy files that the main source files depend
on, but that should not be compiled. It sets the \texttt{-\/-soyDeps}
argument. \texttt{soySkipMetalGeneration} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to just compile Soy files,
without adding Metal.js generated code, like the \texttt{component}
class. It sets the \texttt{-\/-soySkipMetalGeneration} argument.
\texttt{soySrcIncludes} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The path GLOBs of the Soy files to compile. It sets the
\texttt{-\/-soySrc} argument. \texttt{srcIncludes} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The path GLOBs of the JS files to compile. It sets the
\texttt{-\/-src} argument.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{int} and \texttt{String} properties to defer evaluation until
task execution.
\subsection{Task Methods}\label{task-methods-5}
Method \textbar{} Description
\texttt{TranspileJSTask\ soyDependency(Iterable\textless{}?\textgreater{}\ soyDependencies)}
\textbar{} Adds path GLOBs of Soy files that the main source files
depend on, but that should not be compiled.
\texttt{TranspileJSTask\ soyDependency(Object...\ soyDependencies)}
\textbar{} Adds path GLOBs of Soy files that the main source files
depend on, but that should not be compiled.
\texttt{TranspileJSTask\ soySrcInclude(Iterable\textless{}?\textgreater{}\ soySrcIncludes)}
\textbar{} Adds path GLOBs of Soy files to compile.
\texttt{TranspileJSTask\ soySrcInclude(Object...\ soySrcIncludes)}
\textbar{} Adds path GLOBs of Soy files to compile.
\texttt{TranspileJSTask\ srcInclude(Iterable\textless{}?\textgreater{}\ srcIncludes)}
\textbar{} Adds path GLOBs of JS files to compile.
\texttt{TranspileJSTask\ srcInclude(Object...\ srcIncludes)} \textbar{}
Adds path GLOBs of JS files to compile.
\chapter{JSDoc Gradle Plugin}\label{jsdoc-gradle-plugin}
The JSDoc Gradle plugin lets you run the
\href{http://usejsdoc.org/}{JSDoc} tool in order to generate
documentation for your project's JavaScript files.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-13}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.jsdoc", version: "2.0.33"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
\end{verbatim}
There are two JSDoc Gradle plugins you can apply to your project:
\begin{itemize}
\item
Apply the \hyperref[jsdoc-plugin]{JSDoc Plugin} to generate JavaScript
documentation for your project:
\begin{verbatim}
apply plugin: "com.liferay.jsdoc"
\end{verbatim}
\item
Apply the \hyperref[appjsdoc-plugin]{App JSDoc Plugin} in a parent
project to generate the JavaScript documentation as a single, combined
HTML document for an application that spans different subprojects,
each one representing a different component of the same application:
\begin{verbatim}
apply plugin: "com.liferay.app.jsdoc"
\end{verbatim}
\end{itemize}
Both plugins automatically apply the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-node}{\texttt{com.liferay.node}}
plugin.
\section{JSDoc Plugin}\label{jsdoc-plugin}
The plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{downloadJSDoc} \textbar{} \texttt{downloadNode} \textbar{}
\texttt{DownloadNodeModuleTask} \textbar{} Downloads JSDoc in the
project's \texttt{node\_modules} directory. \texttt{jsdoc} \textbar{}
\texttt{downloadJSDoc} \textbar{}
\hyperref[jsdoctask]{\texttt{JSDocTask}} \textbar{} Generates API
documentation for the project's JavaScript code.
By default, the \texttt{downloadJSDoc} task downloads version
\texttt{3.5.5} of the \texttt{jsdoc} package. If the project's
\texttt{package.json} file, however, already lists the \texttt{jsdoc}
package in its \texttt{dependencies} or \texttt{devDependencies}, the
\texttt{downloadJSDoc} task is disabled.
The \texttt{jsdoc} task is automatically configured with sensible
defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied:
Property Name \textbar{} Default Value
\hyperref[destinationdir]{\texttt{destinationDir}} \textbar{}
\textbf{If the \texttt{java} plugin is applied:}
\texttt{"\$\{project.docsDir\}/jsdoc"}
\textbf{Otherwise:} \texttt{"\$\{project.buildDir\}/jsdoc"}
\hyperref[sourcedirs]{\texttt{sourceDirs}} \textbar{} The directory
\texttt{META-INF/resources} in the first \texttt{resources} directory of
the \texttt{main} source set (by default,
\texttt{src/main/resources/META-INF/resources}).
\section{AppJSDoc Plugin}\label{appjsdoc-plugin}
To use the App JSDoc plugin, it is required to apply the
\texttt{com.liferay.app.jsdoc} plugin in a parent project (that is, a
project that is a common ancestor of all the subprojects representing
the various components of the app). It is also required to apply the
\hyperref[jsdoc-plugin]{\texttt{com.liferay.jsdoc}} plugin to all the
subprojects that contain JavaScript files.
The App JSDoc plugin adds three tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{appJSDoc} \textbar{} \texttt{downloadJSDoc} \textbar{}
\hyperref[jsdoctask]{\texttt{JSDocTask}} \textbar{} Generates API
documentation for the app's JavaScript code. \texttt{downloadJSDoc}
\textbar{} \texttt{downloadNode} \textbar{}
\texttt{DownloadNodeModuleTask} \textbar{} Downloads JSDoc in the app's
\texttt{node\_modules} directory. \texttt{jarAppJSDoc} \textbar{}
\texttt{appJSDoc} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html}{\texttt{Jar}}
\textbar{} Assembles a JAR archive containing the JavaScript
documentation files for this app.
By default, the \texttt{downloadJSDoc} task downloads version
\texttt{3.5.5} of the \texttt{jsdoc} package. If the project's
\texttt{package.json} file, however, already lists the \texttt{jsdoc}
package in its \texttt{dependencies} or \texttt{devDependencies}, the
\texttt{downloadJSDoc} task is disabled.
The \texttt{appJSDoc} task is automatically configured with sensible
defaults:
Property Name \textbar{} Default Value
\hyperref[destinationdir]{\texttt{destinationDir}} \textbar{}
\texttt{\$\{project.buildDir\}/docs/jsdoc}
\hyperref[sourcedirs]{\texttt{sourceDirs}} \textbar{} The sum of all the
\texttt{jsdoc.sourceDirs} values of the subprojects.
\section{Project Extension}\label{project-extension-5}
The App JSDoc plugin exposes the following properties through the
extension named \texttt{appJSDocConfiguration}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{subprojects} \textbar{}
\texttt{Set\textless{}Project\textgreater{}} \textbar{}
\texttt{project.subprojects} \textbar{} The subprojects to include in
the JavaScript documentation of the app.
The same extension exposes the following methods:
Method \textbar{} Description
\texttt{AppJSDocConfigurationExtension\ subprojects(Iterable\textless{}Project\textgreater{}\ subprojects)}
\textbar{} Include additional projects in the JavaScript documentation
of the app.
\texttt{AppJSDocConfigurationExtension\ subprojects(Project...\ subprojects)}
\textbar{} Include additional projects in the JavaScript documentation
of the app.
\section{Tasks}\label{tasks-12}
\section{JSDocTask}\label{jsdoctask}
Tasks of type \texttt{JSDocTask} extend \texttt{ExecuteNodeScriptTask},
so all its properties and methods, such as \texttt{args},
\texttt{inheritProxy}, and \texttt{workingDir}, are available.
They also have the following properties set by default:
Property Name \textbar{} Default Value \texttt{scriptFile} \textbar{}
\texttt{"\$\{downloadJSDoc.moduleDir\}/jsdoc.js"}
\subsection{Task Properties}\label{task-properties-12}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{configuration} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.resources.TextResource.html}{\texttt{TextResource}}
\textbar{} \texttt{null} \textbar{} The JSDoc configuration file. It
sets the \texttt{-\/-configure} argument. \texttt{destinationDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
directory where the JavaScript API documentation files are saved. It
sets the \texttt{-\/-destination} argument. \texttt{packageJsonFile}
\textbar{} \texttt{File} \textbar{}
\texttt{"\$\{project.projectDir\}/package.json"} \textbar{} The path to
the project's package file. It sets the \texttt{-\/-package} argument.
\texttt{sourceDirs} \textbar{} \texttt{FileCollection} \textbar{}
\texttt{{[}{]}} \textbar{} The directories that contains the files to
process. \texttt{readmeFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The path to the project's README file. It sets
the \texttt{-\/-readme} argument. \texttt{tutorialsDir} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} The directory in which
JSDoc should search for tutorials. It sets the \texttt{-\/-tutorials}
argument.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
\chapter{Lang Builder Gradle Plugin}\label{lang-builder-gradle-plugin}
The Lang Builder Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/lang-builder}{Liferay
Lang Builder} tool to sort and translate the language keys in your
project.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-14}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.lang.builder", version: "3.0.12"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.lang.builder"
\end{verbatim}
Since the plugin automatically resolves the Liferay Lang Builder library
as a dependency, you have to configure a repository that hosts the
library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
See
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-generating-translations}{this
page} on the \emph{Liferay Developer Network} for more information about
usage of the Lang Builder Gradle plugin.
\section{Tasks}\label{tasks-13}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildLang} \textbar{} - \textbar{}
\hyperref[buildlangtask]{\texttt{BuildLangTask}} \textbar{} Runs Liferay
Lang Builder to translate language property files.
The \texttt{buildLang} task is automatically configured with sensible
defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied:
Property Name \textbar{} Default Value
\hyperref[langdir]{\texttt{langDir}} \textbar{}
\textbf{If the \texttt{java} plugin is applied:} The directory
\texttt{content} in the first \texttt{resources} directory of the
\texttt{main} source set (by default:
\texttt{src/main/resources/content}).
\textbf{Otherwise:} \texttt{null}
\section{BuildLangTask}\label{buildlangtask}
Tasks of type \texttt{BuildLangTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} Lang Builder command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-lang-builder-dependency]{\texttt{project.configurations.langBuilder}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.lang.builder.LangBuilder"}
\subsection{Task Properties}\label{task-properties-13}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{excludedLanguageIds} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{}
\texttt{{[}"da",\ "de",\ "fi",\ "ja",\ "nl",\ "pt\_PT",\ "sv"{]}}
\textbar{} The language IDs to exclude in the automatic translation. It
sets the \texttt{lang.excluded.language.ids} argument. \texttt{langDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
directory where the language properties files are saved. It sets the
\texttt{lang.dir} argument. \texttt{langFileName} \textbar{}
\texttt{String} \textbar{} \texttt{"Language"} \textbar{} The file name
prefix of the language properties files (e.g.,
\texttt{Language\_it.properties}). It sets the \texttt{lang.file}
argument. \texttt{plugin} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to check for duplicate language keys
between the project and the portal. If
\texttt{portalLanguagePropertiesFile} is not set, this property has no
effect. It sets the \texttt{lang.plugin} argument.
\texttt{portalLanguagePropertiesFile} \textbar{} \texttt{File}
\textbar{} \texttt{null} \textbar{} The \texttt{Language.properties}
file of the portal. It sets the
\texttt{lang.portal.language.properties.file} argument.
\texttt{translate} \textbar{} \texttt{boolean} \textbar{} \texttt{true}
\textbar{} Whether to translate the language keys and generate a
language properties file for each locale that's supported by Liferay. It
sets the \texttt{lang.translate} argument.
\texttt{translateSubscriptionKey} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The subscription key for Microsoft Translation
integration. Subscription to the Translator Text Translation API on
Microsoft Cognitive Services is required. Basic subscriptions, up to 2
million characters a month, are free. See
\href{http://docs.microsofttranslator.com/text-translate.html}{here} for
more information. It sets the \texttt{lang.translate.subscription.key}
argument.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\subsection{Task Methods}\label{task-methods-6}
Method \textbar{} Description
\texttt{BuildLangTask\ excludedLanguageIds(Iterable\textless{}Object\textgreater{}\ excludedLanguageIds)}
\textbar{} Adds language IDs to exclude in the automatic translation.
\texttt{BuildLangTask\ excludedLanguageIds(Object...\ excludedLanguageIds)}
\textbar{} Adds language IDs to exclude in the automatic translation.
\section{Additional Configuration}\label{additional-configuration-7}
There are additional configurations that can help you use the Lang
Builder.
\section{Liferay Lang Builder
Dependency}\label{liferay-lang-builder-dependency}
By default, the plugin creates a configuration called
\texttt{langBuilder} and adds a dependency to the latest released
version of the Liferay Lang Builder. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{langBuilder} configuration:
\begin{verbatim}
dependencies {
langBuilder group: "com.liferay", name: "com.liferay.lang.builder", version: "1.0.31"
}
\end{verbatim}
\chapter{Maven Plugin Builder Gradle
Plugin}\label{maven-plugin-builder-gradle-plugin}
The Maven Plugin Builder Gradle Plugin lets you generate the
\href{https://maven.apache.org/ref/current/maven-plugin-api/plugin.html}{Maven
plugin descriptor} for any
\href{https://maven.apache.org/general.html\#What_is_a_Mojo}{Mojos}
found in your project.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-15}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.maven.plugin.builder", version: "1.2.4"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.maven.plugin.builder"
\end{verbatim}
\section{Tasks}\label{tasks-14}
The plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildPluginDescriptor}
\textbar{}\href{https://docs.gradle.org/current/userguide/java_plugin.html\#sec:compile}{\texttt{compileJava}},
\hyperref[writemavensettings]{\texttt{WriteMavenSettings}} \textbar{}
\hyperref[buildplugindescriptortask]{\texttt{BuildPluginDescriptorTask}}
\textbar{} Generates the Maven plugin descriptor for the project.
\texttt{WriteMavenSettings} \textbar{} - \textbar{}
\hyperref[writemavensettingstask]{\texttt{WriteMavenSettingsTask}}
\textbar{} Writes a temporary Maven settings file to be used during
subsequent Maven invocations.
The Maven Plugin Builder Plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin.
The plugin also adds the following dependencies to tasks defined by the
\href{https://docs.gradle.org/current/userguide/maven_plugin.html}{\texttt{maven}}
plugin:
Name \textbar{} Depends On \texttt{install}, \texttt{uploadArchives},
and all the other tasks of type
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Upload.html}{\texttt{Upload}}
\textbar{} \texttt{buildPluginDescriptor}
The \texttt{buildPluginDescriptor} task is automatically configured with
sensible defaults:
Property Name \textbar{} Default Value
\hyperref[classesdir]{\texttt{classesDir}} \textbar{}
\texttt{sourceSets.main.output.classesDir}
\hyperref[mavenembedderclasspath]{\texttt{mavenEmbedderClasspath}}
\textbar{}
\hyperref[maven-embedder-dependency]{\texttt{configurations.mavenEmbedder}}
\hyperref[mavensettingsfile]{\texttt{mavenSettingsFile}} \textbar{}
\hyperref[outputfile]{\texttt{writeMavenSettings.outputFile}}
\hyperref[outputdir]{\texttt{outputDir}} \textbar{} The directory
\texttt{META-INF/maven} in the first \texttt{resources} directory of the
\texttt{main} source set (by default:
\texttt{src/main/resources/META-INF/maven}).
\hyperref[pomartifactid]{\texttt{pomArtifactId}} \textbar{} The bundle
symbolic name of the project inferred via the
\href{https://github.com/gradle/gradle/blob/master/subprojects/osgi/src/main/java/org/gradle/api/internal/plugins/osgi/OsgiHelper.java}{\texttt{OsgiHelper}}
class. \hyperref[pomgroupid]{\texttt{pomGroupId}} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:group}{\texttt{project.group}}
\hyperref[pomversion]{\texttt{pomVersion}} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:version}{\texttt{project.version}}
(if it ends with \texttt{"-SNAPSHOT"}, the suffix will be removed)
\hyperref[sourcedir]{\texttt{sourceDir}} \textbar{} The first
\texttt{java} directory of the \texttt{main} source set (by default:
\texttt{src/main/java}).
The plugin ensures that the \texttt{processResources} task always runs
before \texttt{buildPluginDescriptor} to let \texttt{processResources}
copy the newly generated files in the
\texttt{buildPluginDescriptor.outputDir} directory.
The \texttt{writeMavenSettings} task is also automatically configured
with sensible defaults:
Property Name \textbar{} Default Value
\hyperref[localrepositorydir]{\texttt{localRepositoryDir}} \textbar{}
\texttt{maven.repo.local} system property
\hyperref[nonproxyhosts]{\texttt{nonProxyHosts}} \textbar{}
\texttt{http.nonProxyHosts} system property
\hyperref[outputfile]{\texttt{outputFile}} \textbar{}
\texttt{"\$\{project.buildDir\}/settings.xml"}
\hyperref[proxyhost]{\texttt{proxyHost}} \textbar{}
\texttt{http.ProxyHost} or \texttt{https.proxyHost} system property
(depending on the protocol of
\hyperref[repositoryurl]{\texttt{repositoryUrl}})
\hyperref[proxypassword]{\texttt{proxyPassword}} \textbar{}
\texttt{http.ProxyPassword} or \texttt{https.proxyPassword} system
property (depending on the protocol of
\hyperref[repositoryurl]{\texttt{repositoryUrl}})
\hyperref[proxyport]{\texttt{proxyPort}} \textbar{}
\texttt{http.ProxyPort} or \texttt{https.proxyPort} system property
(depending on the protocol of
\hyperref[repositoryurl]{\texttt{repositoryUrl}})
\hyperref[proxyuser]{\texttt{proxyUser}} \textbar{}
\texttt{http.ProxyUser} or \texttt{https.proxyUser} system property
(depending on the protocol of
\hyperref[repositoryurl]{\texttt{repositoryUrl}})
\hyperref[repositoryurl]{\texttt{repositoryUrl}} \textbar{}
\texttt{repository.url} system property
If running on JDK8+, the plugin also disables the
\href{http://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html\#BEJEFABE}{\emph{doclint}}
feature in all tasks of type
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html}{\texttt{Javadoc}}.
\section{BuildPluginDescriptorTask}\label{buildplugindescriptortask}
Tasks of type \texttt{BuildPluginDescriptorTask} work by generating a
temporary \texttt{pom.xml} file based on the project, and then invoking
the \href{http://maven.apache.org/ref/3.3.9/maven-embedder/}{Maven
Embedder} to build the Maven plugin descriptor.
It is possible to declare information for the plugin descriptor
generation using either
\href{https://maven.apache.org/plugin-tools/maven-plugin-tools-annotations/}{Java
5 Annotations} or
\href{https://maven.apache.org/plugin-tools/maven-plugin-tools-java/}{Javadoc
Tags}.
\subsection{Task Properties}\label{task-properties-14}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{classesDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory that contains the compiled
classes. It sets the value of the
\href{http://maven.apache.org/ref/3.3.9/maven-model/maven.html\#class_build}{\texttt{build.outputDirectory}}
element in the generated \texttt{pom.xml} file.
\texttt{configurationScopeMappings} \textbar{}
\texttt{Map\textless{}String,\ String\textgreater{}} \textbar{}
\texttt{{[}"compile":\ "compile",\ "provided",\ "provided"{]}}
\textbar{} The mapping between the configuration names in the Gradle
project and the
\href{https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html\#Dependency_Scope}{dependency
scopes} in the \texttt{pom.xml} file. It is used to add
\href{http://maven.apache.org/ref/3.3.3/maven-model/maven.html\#class_dependency}{\texttt{dependencies.dependency}}
elements in the generated \texttt{pom.xml} file.
\texttt{forcedExclusions} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The \emph{group:name:version} notation of the dependencies to
always exclude from the ones added in the \texttt{pom.xml} file. It adds
\href{http://maven.apache.org/ref/3.3.3/maven-model/maven.html\#class_exclusion}{\texttt{dependencies.dependency.exclusions.exclusion}}
elements to the generated \texttt{pom.xml} file. \texttt{goalPrefix}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The goal
prefix for the Maven plugin specified in the descriptor. It sets the
value of the
\href{https://maven.apache.org/plugin-tools/maven-plugin-plugin/examples/generate-descriptor.html}{\texttt{build.plugins.plugin.configuration.goalPrefix}}
element in the generated \texttt{pom.xml} file. \texttt{mavenDebug}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to invoke the Maven Embedder in debug mode.
\texttt{mavenEmbedderClasspath} \textbar{} \texttt{FileCollection}
\textbar{} \texttt{null} \textbar{} The classpath used to invoke the
Maven Embedder. \texttt{mavenEmbedderMainClassName} \textbar{}
\texttt{String} \textbar{} \texttt{"org.apache.maven.cli.MavenCli"}
\textbar{} The Maven Embedder's main class name.
\texttt{mavenPluginPluginVersion} \textbar{} \texttt{String} \textbar{}
\texttt{"3.4"} \textbar{} The version of the
\href{https://maven.apache.org/plugin-tools/maven-plugin-plugin/}{Maven
Plugin Plugin} to use to generate the plugin descriptor for the project.
\texttt{mavenSettingsFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The custom \texttt{settings.xml} file to use.
It sets the \texttt{-\/-settings} argument on the Maven Embedder
invocation. \texttt{outputDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory where the Maven plugin descriptor
files are saved. \texttt{pomArtifactId} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The identifier for the artifact that
is unique within the group. It sets the value of the
\href{http://maven.apache.org/ref/3.3.3/maven-model/maven.html\#class_project}{\texttt{project.artifactId}}
element in the generated \texttt{pom.xml} file. \texttt{pomGroupId}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The
universally unique identifier for the project. It sets the value of the
\href{http://maven.apache.org/ref/3.3.3/maven-model/maven.html\#class_project}{\texttt{project.groupId}}
element in the generated \texttt{pom.xml} file. \texttt{pomRepositories}
\textbar{} \texttt{Map\textless{}String,\ Object\textgreater{}}
\textbar{}
\texttt{{[}"liferay-public":\ "http://repository.liferay.com/nexus/content/groups/public"{]}}
\textbar{} The name and URL of the remote repositories. It adds
\href{http://maven.apache.org/ref/3.3.3/maven-model/maven.html\#class_repository}{\texttt{repositories.repository}}
elements in the generated \texttt{pom.xml} file. \texttt{pomVersion}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The
version of the artifact produced by this project. It sets the value of
the
\href{http://maven.apache.org/ref/3.3.3/maven-model/maven.html\#class_project}{\texttt{project.version}}
element in the generated \texttt{pom.xml} file. \texttt{sourceDir}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The
directory that contains the source files. It sets the value of the
\href{http://maven.apache.org/ref/3.3.9/maven-model/maven.html\#class_build}{\texttt{build.sourceDirectory}}
element in the generated \texttt{pom.xml} file.
\texttt{useSetterComments} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to allow
\href{https://maven.apache.org/plugin-tools/maven-plugin-tools-java/}{Mojo
Javadoc Tags} in the setter methods of the Mojo.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{Task Methods}\label{task-methods-7}
Method \textbar{} Description
\texttt{void\ configurationScopeMapping(String\ configurationName,\ String\ scope)}
\textbar{} Adds a mapping between a configuration name in the Gradle
project and the dependency scope in the \texttt{pom.xml} file.
\texttt{BuildPluginDescriptorTask\ forcedExclusions(Iterable\textless{}String\textgreater{}\ forcedExclusions)}
\textbar{} Adds \emph{group:name:version} notations of dependencies to
always exclude from the ones added in the \texttt{pom.xml} file.
\texttt{BuildPluginDescriptorTask\ forcedExclusions(String...\ forcedExclusions)}
\textbar{} Adds \emph{group:name:version} notations of dependencies to
always exclude from the ones added in the \texttt{pom.xml} file.
\texttt{BuildPluginDescriptorTask\ pomRepositories(Map\textless{}String,\ ?\textgreater{}\ pomRepositories}
\textbar{} Adds names and URLs of remote repositories in the
\texttt{pom.xml} file.
\texttt{BuildPluginDescriptorTask\ pomRepository(String\ id,\ Object\ url)}
\textbar{} Adds the name and URL of a remote repository in the
\texttt{pom.xml} file.
\section{WriteMavenSettingsTask}\label{writemavensettingstask}
\subsection{Task Properties}\label{task-properties-15}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{localRepositoryDir} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The directory of the system's local
repository. It sets the value of the
\href{https://maven.apache.org/settings.html\#Simple_Values}{\texttt{localRepository}}
element in the generated \texttt{settings.xml} file.
\texttt{nonProxyHosts} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The patterns of the host that should be
accessed without going through the proxy. It sets the value of the
\href{https://maven.apache.org/settings.html\#Proxies}{\texttt{proxies.proxy.nonProxyHosts}}
element in the generated \texttt{settings.xml} file. \texttt{outputFile}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
generated \texttt{settings.xml} file. \texttt{proxyHost} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The host name or
address of the proxy server. It sets the value of the
\href{https://maven.apache.org/settings.html\#Proxies}{\texttt{proxies.proxy.host}}
element in the generated \texttt{settings.xml} file.
\texttt{proxyPassword} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The password to use to access a protected proxy
server. It sets the value of the
\href{https://maven.apache.org/settings.html\#Proxies}{\texttt{proxies.proxy.password}}
element in the generated \texttt{settings.xml} file. \texttt{proxyPort}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The port
number of the proxy server. It sets the value of the
\href{https://maven.apache.org/settings.html\#Proxies}{\texttt{proxies.proxy.port}}
element in the generated \texttt{settings.xml} file. \texttt{proxyUser}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The user
name to use to access a protected proxy server. It sets the value of the
\href{https://maven.apache.org/settings.html\#Proxies}{\texttt{proxies.proxy.username}}
element in the generated \texttt{settings.xml} file.
\texttt{repositoryUrl} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The URL of the repository
\href{https://maven.apache.org/guides/mini/guide-mirror-settings.html\#Using_A_Single_Repository}{mirror}.
It sets the value of the
\href{https://maven.apache.org/settings.html\#Mirrors}{\texttt{mirrors.mirror.url}}
element in the generated \texttt{settings.xml} file.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{Additional Configuration}\label{additional-configuration-8}
There are additional configurations that can help you use the Maven
Plugin Builder.
\section{Maven Embedder Dependency}\label{maven-embedder-dependency}
By default, the plugin creates a configuration called
\texttt{mavenEmbedder} and adds a dependency to the 3.3.9 version of the
Maven Embedder. It is possible to override this setting and use a
specific version of the tool by manually adding a dependency to the
\texttt{mavenEmbedder} configuration:
\begin{verbatim}
dependencies {
mavenEmbedder group: "org.apache.maven", name: "maven-embedder", version: "3.3.9"
mavenEmbedder group: "org.apache.maven.wagon", name: "wagon-http", version: "2.10"
mavenEmbedder group: "org.eclipse.aether", name: "aether-connector-basic", version: "1.0.2.v20150114"
mavenEmbedder group: "org.eclipse.aether", name: "aether-transport-wagon", version: "1.0.2.v20150114"
mavenEmbedder group: "org.slf4j", name: "slf4j-simple", version: "1.7.5"
}
\end{verbatim}
\section{System Properties}\label{system-properties-2}
It is possible to set the default value of the \texttt{mavenDebug}
property for a \texttt{BuildPluginDescriptorTask} task via system
property:
\begin{itemize}
\tightlist
\item
\texttt{-D\$\{task.name\}.maven.debug=true}
\end{itemize}
For example, run the following Bash command to invoke the Maven Embedder
in debug mode to attach a remote debugger:
\begin{verbatim}
./gradlew buildPluginDescriptor -DbuildPluginDescriptor.maven.debug=true
\end{verbatim}
\chapter{Node Gradle Plugin}\label{node-gradle-plugin}
The Node Gradle plugin lets you run \href{https://nodejs.org/}{Node.js}
and \href{https://www.npmjs.com/}{NPM} as part of your build.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-16}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.node", version: "4.6.18"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.node"
\end{verbatim}
\section{Project Extension}\label{project-extension-6}
The Node Gradle plugin exposes the following properties through the
extension named \texttt{node}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{download} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to download and use a local and
isolated Node.js distribution instead of the one installed in the
system. \texttt{global} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to use a single Node.js installation
for the whole multi-project build. This reduces the time required to
unpack the Node.js distribution and the time required to download NPM
packages thanks to a shared packages cache. If \texttt{download} is
\texttt{false}, this property has no effect. \texttt{nodeDir} \textbar{}
\texttt{File} \textbar{}
\textbf{If \texttt{global} is \texttt{true}:}
\texttt{"\$\{rootProject.buildDir\}/node"}
\textbf{Otherwise:} \texttt{"\$\{project.buildDir\}/node"}
\textbar{} The directory where the Node.js distribution is unpacked. If
\texttt{download} is \texttt{false}, this property has no effect.
\texttt{nodeUrl} \textbar{} \texttt{String} \textbar{}
\texttt{"http://nodejs.org/dist/v\$\{node.nodeVersion\}/node-v\$\{node.nodeVersion\}-\$\{platform\}-x\$\{bitMode\}.\$\{extension\}"}
\textbar{} The URL of the Node.js distribution to download. If
\texttt{download} is \texttt{false}, this property has no effect.
\texttt{nodeVersion} \textbar{} \texttt{String} \textbar{}
\texttt{"5.5.0"} \textbar{} The version of the Node.js distribution to
use. If \texttt{download} is \texttt{false}, this property has no
effect. \texttt{npmArgs} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The arguments added automatically to every task of type
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}}. \texttt{npmUrl}
\textbar{} \texttt{String} \textbar{}
\texttt{"https://registry.npmjs.org/npm/-/npm-\$\{node.npmVersion\}.tgz"}
\textbar{} The URL of the NPM version to download. If \texttt{download}
is \texttt{false}, this property has no effect. \texttt{npmVersion}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The
version of NPM to use. If \texttt{null}, the version of NPM embedded
inside the Node.js distribution is used. If \texttt{download} is
\texttt{false}, this property has no effect.
It is possible to override the default value of the \texttt{download}
property by setting the \texttt{nodeDownload} project property. For
example, this can be done via command line argument:
\begin{verbatim}
./gradlew -PnodeDownload=false npmInstall
\end{verbatim}
The same extension exposes the following methods:
Method \textbar{} Description
\texttt{NodeExtension\ npmArgs(Iterable\textless{}?\textgreater{}\ npmArgs)}
\textbar{} Adds arguments to automatically add to every task of type
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}}.
\texttt{NodeExtension\ npmArgs(Object...\ npmArgs)} \textbar{} Adds
arguments to automatically add to every task of type
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}}.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for
\texttt{String}, to defer evaluation until execution.
Please note that setting the \texttt{global} property of the
\texttt{node} extension via the command line is not supported. It can
only be set via Gradle script, which can be done by adding the following
code to the \texttt{build.gradle} file in the root of a project (e.g.,
Liferay Workspace):
\begin{verbatim}
allprojects {
plugins.withId("com.liferay.node") {
node.global = true
}
}
\end{verbatim}
\section{Tasks}\label{tasks-15}
The plugin adds a series of tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{cleanNPM} \textbar{} - \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Delete.html}{\texttt{Delete}}
\textbar{} Deletes the \texttt{node\_modules} directory, the
\texttt{npm-shrinkwrap.json} file and the \texttt{package-lock.json}
files from the project, if present. \texttt{downloadNode} \textbar{} -
\textbar{} \hyperref[downloadnodetask]{\texttt{DownloadNodeTask}}
\textbar{} Downloads and unpacks the local Node.js distribution for the
project. If \texttt{node.download} is \texttt{false}, this task is
disabled. \texttt{npmInstall} \textbar{} \texttt{downloadNode}
\textbar{} \hyperref[npminstalltask]{\texttt{NpmInstallTask}} \textbar{}
Runs \texttt{npm\ install} to install the dependencies declared in the
project's \texttt{package.json} file, if present. By default, the task
is \hyperref[npminstallretries]{configured} to run \texttt{npm\ install}
two more times if it fails.
\hyperref[npmrunscript-task]{\texttt{npmRun\$\{script\}}} \textbar{}
\texttt{npmInstall} \textbar{}
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}} \textbar{} Runs the
\texttt{\$\{script\}} NPM script. \texttt{npmPackageLock} \textbar{}
\texttt{cleanNPM}, \texttt{npmInstall} \textbar{}
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/DefaultTask.html}{\texttt{DefaultTask}}
\textbar{} Deletes the NPM files and runs \texttt{npm\ install} to
install the dependencies declared in the project's \texttt{package.json}
file, if present. \texttt{npmShrinkwrap} \textbar{} \texttt{cleanNPM},
\texttt{npmInstall} \textbar{}
\hyperref[npmshrinkwraptask]{\texttt{NpmShrinkwrapTask}} \textbar{}
Locks down the versions of a package's dependencies in order to control
which dependency versions are used.
\section{DownloadNodeTask}\label{downloadnodetask}
The purpose of this task is to download and unpack a Node.js
distribution.
\subsection{Task Properties}\label{task-properties-16}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{nodeDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory where the Node.js distribution is
unpacked. \texttt{nodeExeUrl} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The URL of \texttt{node.exe} to download when
on Windows. \texttt{nodeUrl} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The URL of the Node.js distribution to
download. \texttt{npmUrl} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The URL of the NPM version to download.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{ExecuteNodeTask}\label{executenodetask}
This is the base task to run Node.js in a Gradle build. All tasks of
type \texttt{ExecuteNodeTask} automatically depend on
\hyperref[downloadnode]{\texttt{downloadNode}}.
\subsection{Task Properties}\label{task-properties-17}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{args} \textbar{}
\texttt{List\textless{}Object\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The arguments for the Node.js invocation. \texttt{command}
\textbar{} \texttt{String} \textbar{} \texttt{"node"} \textbar{} The
file name of the executable to invoke. \texttt{environment} \textbar{}
\texttt{Map\textless{}Object,\ Object\textgreater{}} \textbar{}
\texttt{{[}{]}} \textbar{} The environment variables for the Node.js
invocation. \texttt{inheritProxy} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to set the \texttt{http\_proxy},
\texttt{https\_proxy}, and \texttt{no\_proxy} environment variables in
the Node.js invocation based on the values of the system properties
\texttt{https.proxyHost}, \texttt{https.proxyPort},
\texttt{https.proxyUser}, \texttt{https.proxyPassword},
\texttt{https.nonProxyHosts}, \texttt{https.proxyHost},
\texttt{https.proxyPort}, \texttt{https.proxyUser},
\texttt{https.proxyPassword}, and \texttt{https.nonProxyHosts}. If these
environment variables are already set, their values will not be
overwritten. \texttt{nodeDir} \textbar{} \texttt{File} \textbar{}
\textbf{If \hyperref[download]{\texttt{node.download}} is
\texttt{true}:} \hyperref[nodedir]{\texttt{node.nodeDir}}
\textbf{Otherwise:} \texttt{null}
\textbar{} The directory that contains the executable to invoke. If
\texttt{null}, the executable must be available in the system
\texttt{PATH}. \texttt{npmInstallRetries} \textbar{} \texttt{int}
\textbar{} \texttt{0} \textbar{} The number of times the
\texttt{node\_modules} is deleted, the NPM cached data is verified
(\texttt{npm\ cache\ verify}), and \texttt{npm\ install} is retried in
case the Node.js invocation defined by this task fails. This can help
solving corrupted \texttt{node\_modules} directories by re-downloading
the project's dependencies. \texttt{workingDir} \textbar{} \texttt{File}
\textbar{} \texttt{project.projectDir} \textbar{} The working directory
to use in the Node.js invocation.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\subsection{Task Methods}\label{task-methods-8}
Method \textbar{} Description
\texttt{ExecuteNodeTask\ args(Iterable\textless{}?\textgreater{}\ args)}
\textbar{} Adds arguments for the Node.js invocation.
\texttt{ExecuteNodeTask\ args(Object...\ args)} \textbar{} Adds
arguments for the Node.js invocation.
\texttt{ExecuteNodeTask\ environment(Map\textless{}?,\ ?\textgreater{}\ environment)}
\textbar{} Adds environment variables for the Node.js invocation.
\texttt{ExecuteNodeTask\ environment(Object\ key,\ Object\ value)}
\textbar{} Adds an environment variable for the Node.js invocation.
\section{ExecuteNodeScriptTask}\label{executenodescripttask}
The purpose of this task is to execute a Node.js script. Tasks of type
\texttt{ExecuteNodeScriptTask} extend
\hyperref[executenodetask]{\texttt{ExecuteNodeTask}}.
\subsection{Task Properties}\label{task-properties-18}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{scriptFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The Node.js script to execute.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
\section{ExecuteNpmTask}\label{executenpmtask}
The purpose of this task is to execute an NPM command. Tasks of type
\texttt{ExecuteNpmTask} extend
\hyperref[executenodescripttask]{\texttt{ExecuteNodeScriptTask}} with
the following properties set by default:
Property Name \textbar{} Default Value \texttt{command} \textbar{}
\textbf{If \texttt{nodeDir} is \texttt{null}:} \texttt{"npm"}
\textbf{Otherwise:} \texttt{"node"}
\texttt{scriptFile} \textbar{}
\textbf{If \texttt{nodeDir} is \texttt{null}:} \texttt{null}
\textbf{Otherwise:}
\texttt{"\$\{nodeDir\}/lib/node\_modules/npm/bin/npm-cli.js"} or
\texttt{"\$\{nodeDir\}/node\_modules/npm/bin/npm-cli.js"} on Windows.
\subsection{Task Properties}\label{task-properties-19}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{cacheConcurrent} \textbar{} \texttt{boolean}
\textbar{}
\textbf{If \texttt{node.npmVersion} is greater than or equal to
\texttt{5.0.0}, or \texttt{node.nodeVersion} is greater than or equal to
\texttt{8.0.0}:} \texttt{true}
\textbf{Otherwise:} \texttt{false}
\textbar{} Whether to run this task concurrently, in case the version of
NPM in use supports multiple concurrent accesses to the same cache
directory. \texttt{cacheDir} \textbar{} \texttt{File} \textbar{}
\textbf{If \texttt{nodeDir} is \texttt{null}, or
\texttt{node.npmVersion} is greater than or equal to \texttt{5.0.0}, or
\texttt{node.nodeVersion} is greater than or equal to \texttt{8.0.0}:}
\texttt{null}
\textbf{Otherwise:} \texttt{"\$\{nodeDir\}/.cache"}
\textbar{} The location of NPM's cache directory. It sets the
\href{https://docs.npmjs.com/misc/config\#cache}{\texttt{-\/-cache}}
argument. Leave the property \texttt{null} to keep the default value.
\texttt{logLevel} \textbar{} \texttt{String} \textbar{} Value to mirror
the log level set in the task's
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Task.html\#org.gradle.api.Task:logger}{\texttt{logger}}
object. \textbar{} The NPM log level. It sets the
\href{https://docs.npmjs.com/misc/config\#loglevel}{--loglevel}
argument. \texttt{production} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to run in production mode during the
NPM invocation. It sets the
\href{https://docs.npmjs.com/misc/config\#production}{\texttt{-\/-production}}
argument. \texttt{progress} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to show a progress bar during the NPM
invocation. It sets the
\href{https://docs.npmjs.com/misc/config\#progress}{\texttt{-\/-progress}}
argument. \texttt{registry} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The base URL of the NPM package registry. It
sets the
\href{https://docs.npmjs.com/misc/config\#registry}{\texttt{-\/-registry}}
argument. Leave the property \texttt{null} or empty to keep the default
value.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{DownloadNodeModuleTask}\label{downloadnodemoduletask}
The purpose of this task is to download a Node.js package. The packages
are downloaded in the \texttt{\$\{workingDir\}/node\_modules} directory,
which is equal, by default, to the \texttt{node\_modules} directory of
the project. Tasks of type \texttt{DownloadNodeModuleTask} extend
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}} in order to execute
the command
\href{https://docs.npmjs.com/cli/install}{\texttt{npm\ install\ \$\{moduleName\}@\$\{moduleVersion\}}}.
\texttt{DownloadNodeModuleTask} instances are automatically disabled if
the project's \texttt{package.json} file already lists a module with the
same name in its \texttt{dependencies} or \texttt{devDependencies}
object.
\subsection{Task Properties}\label{task-properties-20}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{moduleName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The name of the Node.js package to download.
\texttt{moduleVersion} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The version of the Node.js package to download.
It is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{NpmInstallTask}\label{npminstalltask}
Purpose of these tasks is to install the dependencies declared in a
\texttt{package.json} file. Tasks of type \texttt{NpmInstallTask} extend
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}} in order to run the
command
\href{https://docs.npmjs.com/cli/install}{\texttt{npm\ install}}.
\texttt{NpmInstallTask} instances are automatically disabled if the
\texttt{package.json} file does not declare any dependency in the
\texttt{dependency} or \texttt{devDependencies} object.
\subsection{Task Properties}\label{task-properties-21}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{nodeModulesCacheDir} \textbar{} \texttt{File}
\textbar{} \texttt{null} \textbar{}
The directory where \texttt{node\_modules} directories are cached. By
setting this property, it is possible to cache the
\texttt{node\_modules} directory of a project and avoid unnecessary
invocations of \texttt{npm\ install}, useful especially in Continuous
Integration environments.
The \texttt{node\_modules} directory is cached based on the content of
the project's \texttt{package-lock.json} (or
\texttt{npm-shrinkwrap.json}, or \texttt{package.json} if absent).
Therefore, if \texttt{NpmInstallTask} tasks in multiple projects are
configured with the same \texttt{nodeModulesCacheDir}, and their
\texttt{package-lock.json}, \texttt{npm-shrinkwrap.json} or
\texttt{package.json} declare the same dependencies, their
\texttt{node\_modules} caches will be shared.
This feature is not available if the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-cache}{\texttt{com.liferay.cache}}
plugin is applied.
\texttt{nodeModulesCacheNativeSync} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} \textbar{} Whether to use \texttt{rsync} (on
Linux/macOS) or \texttt{robocopy} (on Windows) to cache and restore the
\texttt{node\_modules} directory. If \texttt{nodeModulesCacheDir} is not
set, this property has no effect. \texttt{nodeModulesDigestFile}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{}
If this property is set, the content of the project's
\texttt{package-lock.json} (or \texttt{npm-shrinkwrap.json}, or
\texttt{package.json} if absent) is checked with the digest from the
\texttt{node\_modules} directory. If the digests match, do nothing. If
the digests don't match, the \texttt{node\_modules} directory is deleted
before running \texttt{npm\ install}.
This feature is not available if the \texttt{com.liferay.cache} plugin
is applied or if the property \texttt{nodeModulesCacheDir} is set.
\texttt{removeShrinkwrappedUrls} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} if the \hyperref[registry]{registry} property has a value,
\texttt{false} otherwise. \textbar{} Whether to temporarily remove all
the hard-coded URLs in the \texttt{from} and \texttt{resolved} fields of
the \texttt{npm-shinkwrap.json} file before invoking
\texttt{npm\ install}. This way, it is possible to force NPM to download
all dependencies from a custom registry declared in the
\hyperref[registry]{\texttt{registry}} property. \texttt{useNpmCI}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to run \texttt{npm\ ci} instead of \texttt{npm\ install}. If the
\texttt{package-lock.json} file does not exist, this property has no
effect.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
\section{NpmShrinkwrapTask}\label{npmshrinkwraptask}
The purpose of this task is to lock down the versions of a package's
dependencies so that you can control exactly which dependency versions
are used when your package is installed. Tasks of type
\texttt{NpmShrinkwrapTask} extend
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}} to execute the
command
\href{https://docs.npmjs.com/cli/shrinkwrap}{\texttt{npm\ shrinkwrap}}.
The generated \texttt{npm-shrinkwrap.json} file is automatically sorted
and formatted, so it's easier to see the changes with the previous
version.
\texttt{NpmShrinkwrapTask} instances are automatically disabled if the
\texttt{package.json} file does not exist.
\subsection{Task Properties}\label{task-properties-22}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{excludedDependencies} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The package names to exclude from the generated
\texttt{npm-shrinkwrap.json} file. \texttt{includeDevDependencies}
\textbar{} \texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether
to include the package's \texttt{devDependencies}. It sets the
\href{https://docs.npmjs.com/cli/shrinkwrap\#other-notes}{\texttt{-\/-dev}}
argument.
It is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\subsection{Task Methods}\label{task-methods-9}
Method \textbar{} Description
\texttt{NpmShrinkwrapTask\ excludeDependencies(Iterable\textless{}?\textgreater{}\ excludedDependencies)}
\textbar{} Adds package names to exclude from the generated
\texttt{npm-shrinkwrap.json} file.
\texttt{NpmShrinkwrapTask\ excludeDependencies(Object...\ excludedDependencies)}
\textbar{} Adds package names to exclude from the generated
\texttt{npm-shrinkwrap.json} file.
\section{PublishNodeModuleTask}\label{publishnodemoduletask}
The purpose of this task is to publish a package to the
\href{https://www.npmjs.com/}{NPM registry}. Tasks of type
\texttt{PublishNodeModuleTask} extend
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}} in order to execute
the command
\href{https://docs.npmjs.com/cli/publish}{\texttt{npm\ publish}}.
These tasks generate a new temporary \texttt{package.json} file in the
directory assigned to the \hyperref[workingdir]{\texttt{workingDir}}
property; then the \texttt{npm\ publish} command is executed. If the
\texttt{package.json} file in that location does not exist, the one in
the root of the project directory (if found) is copied; otherwise, a new
file is created.
The \texttt{package.json} is then processed by adding the values
provided by the task properties, if not already present in the file
itself. It is still possible to override one or more fields of the
\texttt{package.json} file with the values provided by the task
properties by adding one or more keys (e.g., \texttt{"version"}) to the
\texttt{overriddenPackageJsonKeys} property.
\subsection{Task Properties}\label{task-properties-23}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{moduleAuthor} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#people-fields-author-contributors}{\texttt{author}}
field in the generated \texttt{package.json} file.
\texttt{moduleBugsUrl} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#bugs}{\texttt{bugs.url}}
field in the generated \texttt{package.json} file.
\texttt{moduleDescription} \textbar{} \texttt{String} \textbar{}
\texttt{project.description} \textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#description-1}{\texttt{description}}
field in the generated \texttt{package.json} file.
\texttt{moduleKeywords} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#keywords}{\texttt{keywords}}
field in the generated \texttt{package.json} file.
\texttt{moduleLicense} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#license}{\texttt{license}}
field in the generated \texttt{package.json} file. \texttt{moduleMain}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The value
of the
\href{https://docs.npmjs.com/files/package.json\#main}{\texttt{main}}
field in the generated \texttt{package.json} file. \texttt{moduleName}
\textbar{} \texttt{String} \textbar{} Name based on
\href{https://github.com/gradle/gradle/blob/master/subprojects/osgi/src/main/java/org/gradle/api/internal/plugins/osgi/OsgiHelper.java}{\texttt{osgiHelper.bundleSymbolicName}}:
for example, if \texttt{osgiHelper.bundleSymbolicName} is
\texttt{"com.liferay.gradle.plugins.node"}, the default value for the
\texttt{moduleName} property is \texttt{"liferay-gradle-plugins-node"}.
\textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#name}{\texttt{name}}
field in the generated \texttt{package.json} file.
\texttt{moduleRepository} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#repository}{\texttt{repository}}
field in the generated \texttt{package.json} file.
\texttt{moduleVersion} \textbar{} \texttt{String} \textbar{}
\texttt{project.version} \textbar{} The value of the
\href{https://docs.npmjs.com/files/package.json\#version}{\texttt{version}}
field in the generated \texttt{package.json} file.
\texttt{npmEmailAddress} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The email address of the npmjs.com user that
publishes the package. \texttt{npmPassword} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The password of the npmjs.com user
that publishes the package. \texttt{npmUserName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The name of the
npmjs.com user that publishes the package.
\texttt{overriddenPackageJsonKeys} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The field values to override in the generated
\texttt{package.json} file.
\subsection{Task Methods}\label{task-methods-10}
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.3529}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.6471}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Method
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{PublishNodeModuleTask\ overriddenPackageJsonKeys(Iterable\textless{}String\textgreater{}\ overriddenPackageJsonKeys)}
& Adds field values to override in the generated \texttt{package.json}
file. \\
\texttt{PublishNodeModuleTask\ overriddenPackageJsonKeys(String...\ overriddenPackageJsonKeys)}
& Adds field values to override in the generated \texttt{package.json}
file. \\
\end{longtable}
\section{npmRun\$\{script\} Task}\label{npmrunscript-task}
For each \href{https://docs.npmjs.com/misc/scripts}{script} declared in
the \texttt{package.json} file of the project, one task
\texttt{npmRun\$\{script\}} of type
\hyperref[executenpmtask]{\texttt{ExecuteNpmTask}} is added. Each of
these tasks is automatically configured with sensible defaults:
Property Name \textbar{} Default Value \texttt{args} \textbar{}
\texttt{{[}"run-script",\ "\$\{script\}"{]}}
If the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied and the \texttt{package.json} file declares a script
named \texttt{"build"}, the script is executed before the
\texttt{classes} task but after the
\href{https://docs.gradle.org/4.0/userguide/java_plugin.html\#sec:java_resources}{\texttt{processResources}}
task.
If the
\href{https://docs.gradle.org/current/javadoc/org/gradle/language/base/plugins/LifecycleBasePlugin.html}{\texttt{lifecycle-base}}
plugin is applied and the \texttt{package.json} file declares a script
named \texttt{test}, the script is executed when running the
\texttt{check} task.
\chapter{REST Builder Gradle Plugin}\label{rest-builder-gradle-plugin}
The REST Builder Gradle plugin lets you generate a REST layer defined in
the REST Builder \texttt{rest-config.yaml} and
\texttt{rest-openapi.yaml} files.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-17}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.rest.builder", version: "1.0.21"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.portal.tools.rest.builder"
\end{verbatim}
The REST Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin.
Since the plugin automatically resolves the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-rest-builder}{Liferay
REST Builder} library as a dependency, you have to configure a
repository that hosts the library and its transitive dependencies. The
Liferay CDN repository hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-16}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildREST} \textbar{} - \textbar{}
\hyperref[buildresttask]{\texttt{BuildRESTTask}} \textbar{} Runs the
Liferay REST Builder.
\section{BuildRESTTask}\label{buildresttask}
Tasks of type \texttt{BuildRESTTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}}
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} REST Builder command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-rest-builder-dependency]{\texttt{project.configurations.restBuilder}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.portal.tools.rest.builder.RESTBuilder"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:systemProperties}{\texttt{systemProperties}}
\textbar{} \texttt{{[}{]}}
\subsection{Task Properties}\label{task-properties-24}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{copyrightFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The file that contains the copyright header.
\texttt{restConfigDir} \textbar{} \texttt{File}
\textbar{}\texttt{\$\{project.projectDir\}} \textbar{} The directory
that contains the \texttt{rest-config.yaml} and
\texttt{rest-openapi.yaml} files.
In the typical scenario, the \texttt{rest-config.yaml} and
\texttt{rest-openapi.yaml} files are contained in the project directory
of \texttt{my-rest-app-impl}. In the \texttt{build.gradle} of the same
module, apply the \texttt{com.liferay.rest.builder} plugin.
The properties of type \texttt{File} supports any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{Additional Configuration}\label{additional-configuration-9}
There are additional configurations added to use REST Builder.
\section{Liferay REST Builder
Dependency}\label{liferay-rest-builder-dependency}
By default, the plugin creates a configuration called
\texttt{restBuilder} and adds a dependency to the latest released
version of Liferay REST Builder.
\begin{verbatim}
dependencies {
restBuilder group: "com.liferay", name: "com.liferay.portal.tools.rest.builder", version: "1.0.22"
}
\end{verbatim}
\chapter{Service Builder Gradle
Plugin}\label{service-builder-gradle-plugin}
The Service Builder Gradle plugin lets you generate a service layer
defined in a
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} \texttt{service.xml} file.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-18}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.service.builder", version: "2.2.46"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.portal.tools.service.builder"
\end{verbatim}
The Service Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin.
Since the plugin automatically resolves the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-service-builder}{Liferay
Service Builder} library as a dependency, you have to configure a
repository that hosts the library and its transitive dependencies. The
Liferay CDN repository hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-17}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildService} \textbar{} - \textbar{}
\hyperref[buildservicetask]{\texttt{BuildServiceTask}} \textbar{} Runs
the Liferay Service Builder.
The \texttt{buildService} task is automatically configured with sensible
defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/war_plugin.html}{\texttt{war}}
plugin is applied, or whether the
\hyperref[osgimodule]{\texttt{osgiModule}} property is \texttt{true}:
Property Name \textbar{} Default Value
\hyperref[apidir]{\texttt{apiDir}} \textbar{}
\textbf{If the \texttt{war} plugin is applied:}
\texttt{\$\{project.webAppDir\}/WEB-INF/service}
\textbf{Otherwise:} \texttt{null}
\hyperref[hbmfile]{\texttt{hbmFile}} \textbar{}
\textbf{If \texttt{osgiModule} is \texttt{true}:}
\texttt{\$\{buildService.resourcesDir\}/META-INF/module-hbm.xml}
\textbf{Otherwise:}
\texttt{\$\{buildService.resourcesDir\}/META-INF/portlet-hbm.xml}
\hyperref[impldir]{\texttt{implDir}} \textbar{} The first \texttt{java}
directory of the \texttt{main} source set (by default:
\texttt{src/main/java}). \hyperref[inputfile]{\texttt{inputFile}}
\textbar{}
\textbf{If the \texttt{war} plugin is applied:}
\texttt{\$\{project.webAppDir\}/WEB-INF/service.xml}
\textbf{Otherwise:} \texttt{\$\{project.projectDir\}/service.xml}
\hyperref[modelhintsfile]{\texttt{modelHintsFile}} \textbar{} The file
\texttt{META-INF/portlet-model-hints.xml} in the first
\texttt{resources} directory of the \texttt{main} source set (by
default: \texttt{src/main/resources/META-INF/portlet-model-hints.xml}).
\hyperref[pluginname]{\texttt{pluginName}} \textbar{}
\textbf{If \texttt{osgiModule} is \texttt{true}:} \texttt{""}
\textbf{Otherwise:} \texttt{project.name}
\hyperref[pluginname]{\texttt{propsUtil}} \textbar{}
\textbf{If \texttt{osgiModule} is \texttt{true}:}
\texttt{"\$\{bundleSymbolicName\}.util.ServiceProps"}The
\texttt{bundleSymbolicName} of the project is inferred via the
\href{https://github.com/gradle/gradle/blob/master/subprojects/osgi/src/main/java/org/gradle/api/internal/plugins/osgi/OsgiHelper.java}{\texttt{OsgiHelper}}
class.
\textbf{Otherwise:} \texttt{"com.liferay.util.service.ServiceProps"}
\hyperref[resourcesdir]{\texttt{resourcesDir}} \textbar{} The first
\texttt{resources} directory of the \texttt{main} source set (by
default: \texttt{src/main/resources}).
\hyperref[springfile]{\texttt{springFile}} \textbar{}
\textbf{If \texttt{osgiModule} is \texttt{true}:} the file
\texttt{META-INF/spring/module-spring.xml} in the first
\texttt{resources} directory of the \texttt{main} source set (by
default: \texttt{src/main/resources/META-INF/spring/module-spring.xml})
\textbf{Otherwise:} the file \texttt{META-INF/portlet-spring.xml} in the
first \texttt{resources} directory of the \texttt{main} source set (by
default: \texttt{src/main/resources/META-INF/portlet-spring.xml})
\hyperref[sqldir]{\texttt{sqlDir}} \textbar{}
\textbf{If the \texttt{war} plugin is applied:}
\texttt{\$\{project.webAppDir\}/WEB-INF/sql}
\textbf{Otherwise:} The directory \texttt{META-INF/sql} in the first
\texttt{resources} directory of the \texttt{main} source set (by
default: \texttt{src/main/resources/META-INF/sql}).
In the typical scenario of a data-driven Liferay OSGi application split
in \texttt{myapp-app}, \texttt{myapp-service} and \texttt{myapp-web}
modules, the \texttt{service.xml} file is usually contained in the root
directory of \texttt{myapp-service}. In the \texttt{build.gradle} of the
same module, it is enough to apply the
\texttt{com.liferay.service.builder} plugin \hyperref[usage]{as
described}, and then add the following snippet to enable the use of
Liferay Service Builder:
\begin{verbatim}
buildService {
apiDir = "../myapp-api/src/main/java"
testDir = "../myapp-test/src/testIntegration/java"
}
\end{verbatim}
While \texttt{apiDir} is required, the \texttt{testDir} property
assignment can be left out, in which case Arquillian-based integration
test classes are generated.
\section{BuildServiceTask}\label{buildservicetask}
Tasks of type \texttt{BuildWSDDTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}}
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} Service Builder command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-service-builder-dependency]{\texttt{project.configurations.serviceBuilder}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{}
\texttt{"com.liferay.portal.tools.service.builder.ServiceBuilder"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:systemProperties}{\texttt{systemProperties}}
\textbar{} \texttt{{[}"file.encoding":\ "UTF-8"{]}}
\subsection{Task Properties}\label{task-properties-25}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{apiDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} A directory where the service API Java source
files are generated. It sets the \texttt{service.api.dir} argument.
\texttt{autoImportDefaultReferences} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} \textbar{} Whether to automatically add default
references, like \texttt{com.liferay.portal.ClassName},
\texttt{com.liferay.portal.Resource} and
\texttt{com.liferay.portal.User}, to the services. It sets the
\texttt{service.auto.import.default.references} argument.
\texttt{autoNamespaceTables} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to prefix table names by the namespace
specified in the \texttt{service.xml} file. It sets the
\texttt{service.auto.namespace.tables} argument.
\texttt{beanLocatorUtil} \textbar{} \texttt{String} \textbar{}
\texttt{"com.liferay.util.bean.PortletBeanLocatorUtil"} \textbar{} The
fully qualified class name of a bean locator class to use in the
generated service classes. It sets the
\texttt{service.bean.locator.util} argument. \texttt{buildNumber}
\textbar{} \texttt{long} \textbar{} \texttt{1} \textbar{} A specific
value to assign the \texttt{build.number} property in the
\texttt{service.properties} file. It sets the
\texttt{service.build.number} argument. \texttt{buildNumberIncrement}
\textbar{} \texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether
to automatically increment the \texttt{build.number} property in the
\texttt{service.properties} file by one at every service generation. It
sets the \texttt{service.build.number.increment} argument.
\texttt{databaseNameMaxLength} \textbar{} \texttt{int} \textbar{}
\texttt{30} \textbar{} The upper bound for database table and column
name lengths to ensure it works on all databases. It sets the
\texttt{service.database.name.max.length} argument. \texttt{hbmFile}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} A Hibernate
Mapping file to generate. It sets the \texttt{service.hbm.file}
argument. \texttt{implDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} A directory where the service Java source files
are generated. It sets the \texttt{service.impl.dir} argument.
\texttt{inputFile} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} The project's \texttt{service.xml} file. It sets the
\texttt{service.input.file} argument. \texttt{modelHintsConfigs}
\textbar{} \texttt{Set} \textbar{}
\texttt{{[}"classpath*:META-INF/portal-model-hints.xml",\ "META-INF/portal-model-hints.xml",\ "classpath*:META-INF/ext-model-hints.xml",\ "classpath*:META-INF/portlet-model-hints.xml"{]}}
\textbar{} Paths to the model hints files for Liferay Service Builder to
use in generating the service layer. It sets the
\texttt{service.model.hints.configs} argument. \texttt{modelHintsFile}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} A model
hints file for the project. It sets the
\texttt{service.model.hints.file} argument. \texttt{osgiModule}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to generate the service layer for OSGi modules. It sets the
\texttt{service.osgi.module} argument. \texttt{pluginName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} If specified, a
plugin can enable additional generation features, such as \texttt{Clp}
class generation, for non-OSGi modules. It sets the
\texttt{service.plugin.name} argument. \texttt{propsUtil} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The fully qualified
class name of the service properties util class to generate. It sets the
\texttt{service.props.util} argument. \texttt{readOnlyPrefixes}
\textbar{} \texttt{Set} \textbar{}
\texttt{{[}"fetch",\ "get",\ "has",\ "is",\ "load",\ "reindex",\ "search"{]}}
\textbar{} Prefixes of methods to consider read-only. It sets the
\texttt{service.read.only.prefixes} argument.
\texttt{resourceActionsConfigs} \textbar{} \texttt{Set} \textbar{}
\texttt{{[}"META-INF/resource-actions/default.xml",\ "resource-actions/default.xml"{]}}
\textbar{} Paths to the
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{resource
actions} files for Liferay Service Builder to use in generating the
service layer. It sets the \texttt{service.resource.actions.configs}
argument. \texttt{resourcesDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} A directory where the service non-Java files
are generated. It sets the \texttt{service.resources.dir} argument.
\texttt{springFile} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} A service Spring file to generate. It sets the
\texttt{service.spring.file} argument. \texttt{springNamespaces}
\textbar{} \texttt{Set} \textbar{} \texttt{{[}"beans"{]}} \textbar{}
Namespaces of Spring XML Schemas to add to the service Spring file. It
sets the \texttt{service.spring.namespaces} argument. \texttt{sqlDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} A directory
where the SQL files are generated. It sets the \texttt{service.sql.dir}
argument. \texttt{sqlFileName} \textbar{} \texttt{String} \textbar{}
\texttt{"tables.sql"} \textbar{} A name (relative to \texttt{sqlDir})
for the file in which the SQL table creation instructions are generated.
It sets the \texttt{service.sql.file} argument.
\texttt{sqlIndexesFileName} \textbar{} \texttt{String} \textbar{}
\texttt{"indexes.sql"} \textbar{} A name (relative to \texttt{sqlDir})
for the file in which the SQL index creation instructions are generated.
It sets the \texttt{service.sql.indexes.file} argument.
\texttt{sqlSequencesFileName} \textbar{} \texttt{String} \textbar{}
\texttt{"sequences.sql"} \textbar{} A name (relative to \texttt{sqlDir})
for the file in which the SQL sequence creation instructions are
generated. It sets the \texttt{service.sql.sequences.file} argument.
\texttt{targetEntityName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} If specified, it's the name of the entity for
which Liferay Service Builder should generate the service. It sets the
\texttt{service.target.entity.name} argument. \texttt{testDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} If
specified, it's a directory where integration test Java source files are
generated. It sets the \texttt{service.test.dir} argument.
\texttt{uadDir} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} A directory where the UAD (user-associated data) Java source
files are generated. It sets the \texttt{service.uad.dir} argument.
\texttt{uadTestIntegrationDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} A directory where integration test UAD
(user-associated data) Java source files are generated. It sets the
\texttt{service.uad.test.integration.dir} argument.
The properties of type \texttt{File} supports any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{Additional Configuration}\label{additional-configuration-10}
There are additional configurations that can help you use Service
Builder.
\section{Liferay Service Builder
Dependency}\label{liferay-service-builder-dependency}
By default, the plugin creates a configuration called
\texttt{serviceBuilder} and adds a dependency to the latest released
version of Liferay Service Builder. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{serviceBuilder} configuration:
\begin{verbatim}
dependencies {
serviceBuilder group: "com.liferay", name: "com.liferay.portal.tools.service.builder", version: "1.0.292"
}
\end{verbatim}
If you're applying the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins}{\texttt{com.liferay.gradle.plugins}}
or
\href{https://github.com/liferay/liferay-portal/blob/master/modules/sdk/gradle-plugins-workspace}{\texttt{com.liferay.gradle.plugins.workspace}}
plugins to your project, the Service Builder dependency is already added
to the \texttt{serviceBuilder} configuration. Therefore, if you try to
apply a customized version of Service Builder, it's not recognized; you
must override the configuration already applied.
To do this, you must customize the classpath of the
\texttt{buildService} task. If you're supplying the customized Service
Builder plugin through a module named \texttt{custom-sb-api}, you could
modify the \texttt{buildService} task like this:
\begin{verbatim}
buildService {
apiDir = "../custom-sb-api/src/main/java"
classpath = configurations.serviceBuilder.filter { file -> !file.name.contains("com.liferay.portal.tools.service.builder") }
}
\end{verbatim}
If you do this in conjunction with the \texttt{serviceBuilder}
dependency configuration, the custom Service Builder version is used.
\chapter{Source Formatter Gradle
Plugin}\label{source-formatter-gradle-plugin}
The Source Formatter Gradle plugin lets you format project files using
the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/source-formatter}{Liferay
Source Formatter} tool.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-19}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.source.formatter", version: "2.3.413"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.source.formatter"
\end{verbatim}
Since the plugin automatically resolves the Liferay Source Formatter
library as a dependency, you have to configure a repository that hosts
the library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-18}
The plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{checkSourceFormatting} \textbar{} - \textbar{}
\hyperref[formatsourcetask]{\texttt{FormatSourceTask}} \textbar{} Runs
the Liferay Source Formatter to check for source formatting errors.
\texttt{formatSource} \textbar{} - \textbar{}
\hyperref[formatsourcetask]{\texttt{FormatSourceTask}} \textbar{} Runs
the Liferay Source Formatter to format the project files.
If desired, it is possible to check for source formatting errors while
executing the
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#N15056}{\texttt{check}}
task by adding the following dependency:
\begin{verbatim}
check {
dependsOn checkSourceFormatting
}
\end{verbatim}
The same can be achieved by adding the following snippet to the
\texttt{build.gradle} file in the root directory of a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{\emph{Liferay
Workspace}}:
\begin{verbatim}
subprojects {
afterEvaluate {
if (plugins.hasPlugin("base") && plugins.hasPlugin("com.liferay.source.formatter")) {
check.dependsOn checkSourceFormatting
}
}
}
\end{verbatim}
The tasks \texttt{checkSourceFormatting} and \texttt{formatSource} are
automatically skipped if another task with the same name is being
executed in a parent project.
\section{FormatSourceTask}\label{formatsourcetask}
Tasks of type \texttt{FormatSourceTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, like
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}}
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} Source Formatter command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-source-formatter-dependency]{\texttt{project.configurations.sourceFormatter}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.source.formatter.SourceFormatter"}
\subsection{Task Properties}\label{task-properties-26}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{autoFix} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to automatically fix source formatting
errors. It sets the \texttt{source.auto.fix} argument. \texttt{baseDir}
\textbar{} \texttt{File} \textbar{} \textbar{} The Source Formatter base
directory. It sets the \texttt{source.base.dir} argument.
\emph{(Read-only)} \texttt{baseDirName} \textbar{} \texttt{String}
\textbar{} \texttt{"./"} \textbar{} The name of the Source Formatter
base directory, relative to the project directory.
\texttt{fileExtensions} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The file extensions to format. If empty, all file extensions
will be formatted. It sets the \texttt{source.file.extensions} argument.
\texttt{files} \textbar{} \texttt{List\textless{}File\textgreater{}}
\textbar{} \textbar{} The list of files to format. It sets the
\texttt{source.files} argument. \emph{(Read-only)} \texttt{fileNames}
\textbar{} \texttt{List\textless{}String\textgreater{}} \textbar{}
\texttt{null} \textbar{} The file names to format, relative to the
project directory. If \texttt{null}, all files contained in
\texttt{baseDir} will be formatted. \texttt{formatCurrentBranch}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to format only the files contained in \texttt{baseDir} that are added or
modified in the current Git branch. It sets the
\texttt{format.current.branch} argument. \texttt{formatLatestAuthor}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to format only the files contained in \texttt{baseDir} that are added or
modified in the latest Git commits of the same author. It sets the
\texttt{format.latest.author} argument. \texttt{formatLocalChanges}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to format only the unstaged files contained in \texttt{baseDir}. It sets
the \texttt{format.local.changes} argument.
\texttt{gitWorkingBranchName} \textbar{} \texttt{String} \textbar{}
\texttt{"master"} \textbar{} The Git working branch name. It sets the
\texttt{git.working.branch.name} argument.
\texttt{includeSubrepositories} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to format files that are in read-only
subrepositories. It sets the \texttt{include.subrepositories} argument.
\texttt{maxLineLength} \textbar{} \texttt{int} \textbar{} \texttt{80}
\textbar{} The maximum number of characters allowed in Java files. It
sets the \texttt{max.line.length} argument. \texttt{printErrors}
\textbar{} \texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether
to print formatting errors on the Standard Output stream. It sets the
\texttt{source.print.errors} argument. \texttt{processorThreadCount}
\textbar{} \texttt{int} \textbar{} \texttt{5} \textbar{} The number of
threads used by Source Formatter. It sets the
\texttt{processor.thread.count} argument. \texttt{showDebugInformation}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to show debug information, if present. It sets the
\texttt{show.debug.information} argument. \texttt{showDocumentation}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to show the documentation for the source formatting issues, if present.
It sets the \texttt{show.documentation} argument.
\texttt{showStatusUpdates} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to show status updates during source
formatting, if present. It sets the \texttt{show.status.updates}
argument. \texttt{throwException} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to fail the build if formatting errors
are found. It sets the \texttt{source.throw.exception} argument.
\section{Additional Configuration}\label{additional-configuration-11}
There are additional configurations that can help you use the Source
Formatter.
\section{Liferay Source Formatter
Dependency}\label{liferay-source-formatter-dependency}
By default, the plugin creates a configuration called
\texttt{sourceFormatter} and adds a dependency to the latest released
version of Liferay Source Formatter. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{sourceFormatter} configuration:
\begin{verbatim}
dependencies {
sourceFormatter group: "com.liferay", name: "com.liferay.source.formatter", version: "1.0.885"
}
\end{verbatim}
\section{System Properties}\label{system-properties-3}
It is possible to set the default values of the \texttt{fileExtensions},
\texttt{fileNames}, \texttt{formatCurrentBranch},
\texttt{formatLatestAuthor}, and \texttt{formatLocalChanges} properties
for a \texttt{FormatSourceTask} task via system properties:
\begin{itemize}
\tightlist
\item
\texttt{-D\$\{task.name\}.file.extensions=java,xml}
\item
\texttt{-D\$\{task.name\}.file.names=README.markdown,src/main/resources/hello.txt}
\item
\texttt{-D\$\{task.name\}.format.current.branch=true}
\item
\texttt{-D\$\{task.name\}.format.latest.author=true}
\item
\texttt{-D\$\{task.name\}.format.local.changes=true}
\end{itemize}
For example, run the following Bash command to format only the unstaged
files in the project:
\begin{verbatim}
./gradlew formatSource -DformatSource.format.local.changes=true
\end{verbatim}
\chapter{Soy Gradle Plugin}\label{soy-gradle-plugin}
The Soy Gradle plugin lets you compile
\href{https://developers.google.com/closure/templates/}{Closure
Templates} into JavaScript functions. It also lets you use a custom
localization mechanism in the generated \texttt{.soy.js} files by
replacing
\href{https://developers.google.com/closure/templates/docs/translation\#closurecompiler}{\texttt{goog.getMsg}}
definitions with a different function call (e.g.,
\texttt{Liferay.Language.get}).
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-20}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.soy", version: "3.1.8"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
\end{verbatim}
There are two Soy Gradle plugins you can apply to your project:
\begin{itemize}
\item
Apply the \hyperref[soy-plugin]{\emph{Soy Plugin}} to compile Closure
Templates into JavaScript functions:
\begin{verbatim}
apply plugin: "com.liferay.soy"
\end{verbatim}
\item
Apply the \hyperref[soy-translation-plugin]{\emph{Soy Translation
Plugin}} to use a custom localization mechanism in the generated
\texttt{.soy.js} files:
\begin{verbatim}
apply plugin: "com.liferay.soy.translation"
\end{verbatim}
\end{itemize}
Since the Soy Gradle plugin automatically resolves the Soy library as a
dependency, you have to configure a repository that hosts the library
and its transitive dependencies. The Liferay CDN repository hosts them
all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Soy Plugin}\label{soy-plugin}
The Soy plugin adds two tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildSoy} \textbar{} - \textbar{}
\hyperref[buildsoytask]{\texttt{BuildSoyTask}} \textbar{} Compiles
Closure Templates into JavaScript functions.
\texttt{wrapSoyAlloyTemplate} \textbar{} - \texttt{configJSModules} if
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-js-module-config-generator}{\texttt{com.liferay.js.module.config.generator}}
is applied - \texttt{processResources} if \texttt{java} is applied -
\texttt{transpileJS} if
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-js-transpiler}{\texttt{com.liferay.js.transpiler}}
is applied \textbar{}
\hyperref[wrapsoyalloytemplatetask]{\texttt{WrapSoyAlloyTemplateTask}}
\textbar{} Wraps the JavaScript functions compiled from Closure
Templates into AlloyUI modules.
The plugin also adds the following dependencies to tasks defined by the
\texttt{java} plugin:
Name \textbar{} Depends On \texttt{classes} \textbar{}
\texttt{wrapSoyAlloyTemplate}
The \texttt{buildSoy} task is automatically configured with sensible
defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:includes}{\texttt{includes}}
\textbar{} \texttt{{[}"**/*.soy"{]}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:source}{\texttt{source}}
\textbar{}
\textbf{If the \texttt{java} plugin is applied:} The first
\texttt{resources} directory of the \texttt{main} source set (by
default, \texttt{src/main/resources}).
\textbf{Otherwise:} \texttt{{[}{]}}
The \texttt{wrapSoyAlloyTemplate} task is \textbf{disabled by default},
and it is automatically configured with sensible defaults, depending on
whether the \texttt{java} plugin is applied:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Task.html\#org.gradle.api.Task:enabled}{\texttt{enabled}}
\textbar{} \texttt{false}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:includes}{\texttt{includes}}
\textbar{} \texttt{{[}"**/*.soy.js"{]}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:source}{\texttt{source}}
\textbar{}
\textbf{If the \texttt{java} plugin is applied:}
\texttt{project.sourceSets.main.output.resourcesDir}
\textbf{Otherwise:} \texttt{{[}{]}}
\section{Additional Configuration}\label{additional-configuration-12}
There are additional configurations that can help you use the Soy
library.
\subsection{Soy Dependency}\label{soy-dependency}
By default, the plugin creates a configuration called \texttt{soy} and
adds a dependency to the \texttt{2015-04-10} version of the Soy library.
It is possible to override this setting and use a specific version of
the tool by manually adding a dependency to the \texttt{soy}
configuration:
\begin{verbatim}
dependencies {
soy group: "com.google.template", name: "soy", version: "2015-04-10"
}
\end{verbatim}
\section{Soy Translation Plugin}\label{soy-translation-plugin}
The Soy Translation plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{replaceSoyTranslation} \textbar{} - \texttt{configJSModules} if
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-js-module-config-generator}{\texttt{com.liferay.js.module.config.generator}}
is applied - \texttt{processResources} if \texttt{java} is applied -
\texttt{transpileJS} if
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-js-transpiler}{\texttt{com.liferay.js.transpiler}}
is applied \textbar{}
\hyperref[replacesoytranslationtask]{\texttt{ReplaceSoyTranslationTask}}
\textbar{} Replaces \texttt{goog.getMsg} definitions with
\texttt{Liferay.Language.get} calls.
The plugin also adds the following dependencies to tasks defined by the
\texttt{java} plugin:
Name \textbar{} Depends On \texttt{classes} \textbar{}
\texttt{replaceSoyTranslation}
The \texttt{replaceSoyTranslation} task is automatically configured with
sensible defaults, depending on whether the \texttt{java} plugin is
applied:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:includes}{\texttt{includes}}
\textbar{} \texttt{{[}"**/*.soy.js"{]}}
\hyperref[replacementclosure]{\texttt{replacementClosure}} \textbar{}
Replaces \texttt{goog.getMsg} definitions with
\texttt{Liferay.Language.get} calls.
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:source}{\texttt{source}}
\textbar{}
\textbf{If the \texttt{java} plugin is applied:}
\texttt{project.sourceSets.main.output.resourcesDir}
\textbf{Otherwise:} \texttt{{[}{]}}
\section{Tasks}\label{tasks-19}
\section{BuildSoyTask}\label{buildsoytask}
Tasks of type \texttt{BuildSoyTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:include(java.lang.Iterable)}{\texttt{include}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:exclude(java.lang.Iterable)}{\texttt{exclude}},
are available.
\subsection{Task Properties}\label{task-properties-27}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{classpath} \textbar{}
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileCollection.html}{\texttt{FileCollection}}
\textbar{}
\hyperref[soy-dependency]{\texttt{project.configurations.soy}}
\textbar{} The classpath for executing the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-soy-builder}{Liferay
Portal Tools Soy Builder}.
\section{WrapSoyAlloyTemplateTask}\label{wrapsoyalloytemplatetask}
Tasks of type \texttt{WrapSoyAlloyTemplateTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:include(java.lang.Iterable)}{\texttt{include}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:exclude(java.lang.Iterable)}{\texttt{exclude}},
are available.
\subsection{Task Properties}\label{task-properties-28}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{moduleName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The name of the AlloyUI module.
\texttt{namespace} \textbar{} \texttt{String} \textbar{} \texttt{null}
\textbar{} The namespace of the Closure Templates of the project.
It is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\section{ReplaceSoyTranslationTask}\label{replacesoytranslationtask}
The \texttt{ReplaceSoyTranslationTask} task type finds all the
\texttt{goog.getMsg} definitions in the project's files and replaces
them with a custom function call.
\begin{verbatim}
var MSG_EXTERNAL_123 = goog.getMsg('welcome-to-{$releaseInfo}', { 'releaseInfo': opt_data.releaseInfo });
\end{verbatim}
A \texttt{goog.getMsg} definition looks like the example above, and it
has the following components:
\begin{itemize}
\tightlist
\item
\emph{variable name}: \texttt{MSG\_EXTERNAL\_123}
\item
\emph{language key}: \texttt{welcome-to-\{\$releaseInfo\}}
\item
\emph{arguments object}:
\texttt{\{\ \textquotesingle{}releaseInfo\textquotesingle{}:\ opt\_data.releaseInfo\ \}}
\end{itemize}
Tasks of type \texttt{ReplaceSoyTranslationTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:include(java.lang.Iterable)}{\texttt{include}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:exclude(java.lang.Iterable)}{\texttt{exclude}},
are available.
\subsection{Task Properties}\label{task-properties-29}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{replacementClosure} \textbar{}
\texttt{Closure\textless{}String\textgreater{}} \textbar{} \texttt{null}
\textbar{} The Closure invoked in order to get the replacement for
\texttt{goog.getMsg} definitions. The given Closure is passed the
\emph{variable name}, \emph{language key}, and \emph{arguments object}
as its parameters.
\chapter{Target Platform Gradle
Plugin}\label{target-platform-gradle-plugin}
The Target Platform Gradle plugin helps with building multiple projects
against a declared API target platform. Java dependencies can be managed
with Maven BOMs and OSGi modules can be resolved against an OSGi
distribution.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-21}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.target.platform", version: "2.0.0"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
\end{verbatim}
There are two Target Platform Gradle plugins you can apply to your
project. If you have a multi-module Gradle project, you only need to
apply these plugins to the root project.
\begin{itemize}
\item
The \hyperref[target-platform-plugin]{\emph{Target Platform Plugin}}
helps to configure your projects to build against an established set
of platform artifacts, including Java and OSGi dependencies.
\begin{verbatim}
apply plugin: "com.liferay.target.platform"
\end{verbatim}
\item
The \hyperref[target-platform-ide-plugin]{\emph{Target Platform IDE
Plugin}} is a superset of the Target Platform Plugin (it applies the
above plugin) and also adds IDE integration for searching and
debugging source code in the target platform artifacts.
\begin{verbatim}
apply plugin: "com.liferay.target.platform.ide"
\end{verbatim}
\end{itemize}
Since the plugin automatically resolves target platform configurations
as dependencies, you must configure a repository that hosts these
artifacts. The Liferay CDN repository hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Target Platform Plugin}\label{target-platform-plugin}
The plugin applies the
\href{https://github.com/spring-gradle-plugins/dependency-management-plugin}{Spring
Dependency Management Plugin} and then adds several specific
configurations to configure the BOMs that are imported to manage Java
dependencies and the various artifacts used in resolving OSGi
dependencies. Also, a new \texttt{resolve} task is added to resolve all
OSGi requirements against a declared distribution artifact.
The plugin adds a series of configurations to your project:
Name \textbar{} Description \texttt{targetPlatformBoms} \textbar{}
Configures all the BOMs to import as managed dependencies.
\texttt{targetPlatformBundles} \textbar{} Configures all the bundles in
addition to the distro to resolve against. \texttt{targetPlatformDistro}
\textbar{} Configures the distro JAR file to use as base for resolving
against. \texttt{targetPlatformRequirements} \textbar{} Configures the
list of JAR files to use as run requirements for resolving.
The plugin adds a task \texttt{resolve} of type
\hyperref[resolvetask]{\texttt{ResolveTask}} to your project that
performs an OSGi resolve operation using the
\texttt{targetPlatformRequirements} configuration as the basis of the
requirements. The \texttt{targetPlatformBundles} configuration is used
as a repository for the resolver to resolve requirements. Lastly, the
\texttt{targetPlatformDistro} configuration is used to provide the
\emph{distro} artifact for the resolve process. The \emph{distro} is the
artifact that provides all the OSGi capabilities of the target platform.
All of these parameters are used to create a \texttt{bndrun} file that
can be used as input into the Bndrun resolve operation.
\section{Target Platform IDE Plugin}\label{target-platform-ide-plugin}
The plugin applies the \hyperref[target-platform-plugin]{Target
Platform} and the
\href{https://docs.gradle.org/current/userguide/eclipse_plugin.html}{\texttt{eclipse}}
plugins to your project, and also adds a special
\texttt{targetPlatformIDE} configuration, which is used to configure
both the \texttt{eclipse} model and \texttt{idea} plugin model in Gradle
to add all target platform artifacts to the classpath so they are
visible to both Eclipse and IntelliJ's Java Model Search (for looking up
sources to classes).
\section{Project Extension}\label{project-extension-7}
The Target Platform plugin exposes the following properties through the
extension named \texttt{targetPlatform}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{ignoreResolveFailures} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} \textbar{} Whether to ignore resolve failures
found when executing tasks of type
\hyperref[resolvetask]{\texttt{ResolveTask}}. \texttt{subprojects}
\textbar{} \texttt{Set\textless{}Project\textgreater{}} \textbar{}
\texttt{project.subprojects} \textbar{} The subprojects to configure
with target platform support, including dependency management and the
\texttt{resolve} task.
The same extension exposes the following methods:
Method \textbar{} Description
\texttt{TargetPlatformExtension\ applyToConfiguration(Iterable\textless{}?\textgreater{}\ configurationNames)}
\textbar{} Adds additional configurations to configure the BOMs that are
imported to manage Java dependencies and the various artifacts used in
resolving OSGi dependencies.
\texttt{TargetPlatformExtension\ applyToConfiguration(Object...\ configurationNames)}
\textbar{} Adds additional configurations to configure the BOMs that are
imported to manage Java dependencies and the various artifacts used in
resolving OSGi dependencies.
\texttt{TargetPlatformExtension\ onlyIf(Closure\textless{}Boolean\textgreater{}\ onlyIfClosure)}
\textbar{} Includes a subproject in the target platform configuration if
the given closure returns \texttt{true}. The closure is evaluated at the
end of the subproject configuration phase and is passed a single
parameter: the subproject. If the closure returns \texttt{false}, the
subproject is not included in the target platform configuration
\texttt{TargetPlatformExtension\ onlyIf(Spec\textless{}Project\textgreater{}\ onlyIfSpec)}
\textbar{} Includes a subproject in the target platform configuration if
the given spec is satisfied. The spec is evaluated at the end of the
subproject configuration phase. If the spec is not satisfied, the
subproject is not included in the target platform configuration.
\texttt{TargetPlatformExtension\ resolveOnlyIf(Closure\textless{}Boolean\textgreater{}\ resolveOnlyIfClosure)}
\textbar{} Includes a subproject in the resolving process (including
both the requirements and bundles configuration) if the given closure
returns \texttt{true}. The closure is evaluated at the end of the
subproject configuration phase and is passed a single parameter: the
subproject. If the closure returns \texttt{false}, the subproject is the
resolution process.
\texttt{TargetPlatformExtension\ resolveOnlyIf(Spec\textless{}Project\textgreater{}\ resolveOnlyIfSpec)}
\textbar{} Includes a subproject in the resolving platform configuration
if the given spec is satisfied. The spec is evaluated at the end of the
subproject configuration phase. If the spec is not satisfied, the
subproject is not included in the target platform configuration.
\texttt{TargetPlatformExtension\ subprojects(Iterable\textless{}Project\textgreater{}\ subprojects)}
\textbar{} Includes additional projects to be configured with Target
Platform support.
\texttt{TargetPlatformExtension\ subprojects(Project...\ subprojects)}
\textbar{} Includes additional projects to be configured with Target
Platform support.
\section{Tasks}\label{tasks-20}
\section{ResolveTask}\label{resolvetask}
The purpose of this task is to resolve an OSGi module (or all OSGi
modules of subprojects) against the available
\texttt{targetPlatformBundles} and \texttt{targetPlatformDistro}
configurations. By default, the \texttt{targetPlatformBundles} are all
the artifacts created by all the subprojects. The
\texttt{targetPlatformDistro} must be set explicitly to a valid
distribution artifact. When the task is performed, a \texttt{bndrun}
file is generated using the specified \texttt{targetPlatformDistro} as
the \texttt{-distro} instruction; the \texttt{-runrequirements} are a
set of \texttt{osgi.identity} requirements for the
\texttt{targetPlatformRequirements} configuration. If the resolve
operation is able to find a valid set of \texttt{-runbundles} that match
the \texttt{-runrequirements}, then the task passes successfully (the
resolution is valid). If a set of run bundles can't be found, the
resolution has failed and the failed requirements are listed as output
of the task.
\subsection{Task Properties}\label{task-properties-30}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{bndrunFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} If this property is specified, it is used as
the \texttt{bndrun} file to input into the resolver.
\texttt{bundlesFileCollection} \textbar{} \texttt{FileCollection}
\textbar{} All JAR files of subprojects with \texttt{jar} task
\textbar{} The input to \texttt{bndrun} resolve operation.
\texttt{distroFileCollection} \textbar{} \texttt{FileCollection}
\textbar{} \texttt{null} \textbar{} The \emph{distro} parameter for the
generated \texttt{bndrun} file. \texttt{ignoreFailures} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether the
\texttt{resolve} task should ignore failing the build for resolution
errors. \texttt{offline} \textbar{} \texttt{boolean} \textbar{}
\texttt{null} \textbar{} Whether to run the bndrun resolve operation in
offline mode. \texttt{requirementsFileCollection} \textbar{}
\texttt{FileCollection} \textbar{}
\textbf{For the root project:} All the output JAR files of the
subprojects.
\textbf{For subprojects:} The output JAR file of the subproject.
\textbar{} For each resolve operation, the requirements must be
specified in the \texttt{bndrun} file; each of the JARs in this
collection generate an \texttt{osgi.identify} requirement in the
\texttt{bndrun} file.
\section{Additional Configuration}\label{additional-configuration-13}
There are additional configurations that you can use to configure the
target platform.
\section{Target Platform BOMs
Dependency}\label{target-platform-boms-dependency}
The plugin creates a configuration called \texttt{targetPlatformBoms}
with no defaults. You can use this dependency to set which BOMs to
import to configure your target platform.
\begin{verbatim}
dependencies {
targetPlatformBoms group: "com.liferay.portal", name: "release.portal.bom", version: "7.2.0"
targetPlatformBoms group: "com.liferay.portal", name: "release.portal.bom.compile.only", version: "7.2.0"
}
\end{verbatim}
\section{Target Platform Bundles
Dependency}\label{target-platform-bundles-dependency}
The plugin creates a configuration called
\texttt{targetPlatformBundles}. It is configured with default
dependencies to all resolvable bundles in a multi-project build (e.g.,
all projects in multi-project build that have a \texttt{jar} task). This
can be used to specify additional bundles that should be added to the
set of bundles given to \texttt{resolve} task to resolve against when
checking for OSGi requirements.
\begin{verbatim}
dependencies {
targetPlatformBundles group: "com.google.guava", name: "guava", version: "23.0"
}
\end{verbatim}
\section{Target Platform Distro
Dependency}\label{target-platform-distro-dependency}
The plugin creates a configuration called \texttt{targetPlatformDistro}.
It is has no default so you must specify which artifact you want to use
as the distribution to resolve against.
\begin{verbatim}
dependencies {
targetPlatformDistro group: "com.liferay.portal", name: "release.portal.distro", version: "7.2.0"
}
\end{verbatim}
If you have created your own custom distro JAR that is available
locally, you can use the \texttt{files} method to add it to the
configuration.
\begin{verbatim}
dependencies {
targetPlatformDistro files("custom-distro.jar")
}
\end{verbatim}
\section{Target Platform Requirements
Dependency}\label{target-platform-requirements-dependency}
The plugin creates a configuration called
\texttt{targetPlatformRequirements}. It is configured with default
dependencies to all resolvable bundles in a multi-project build (e.g.,
all projects in multi-project build that have a \texttt{jar} task). This
is can be used to specify additional bundles that should be added to the
set of bundles given to the \texttt{resolve} task to set as
\texttt{osgi.identity} requirements.
\begin{verbatim}
dependencies {
targetPlatformRequirements group: "com.liferay", name: "com.liferay.other.bundle", version: "1.0"
}
\end{verbatim}
\chapter{Theme Builder Gradle Plugin}\label{theme-builder-gradle-plugin}
The Theme Builder Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-theme-builder}{Liferay
Theme Builder} tool to build the Liferay theme files in your project.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-22}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.theme.builder", version: "2.0.7"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.portal.tools.theme.builder"
\end{verbatim}
The Theme Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/war_plugin.html}{\texttt{war}}
plugin. It also applies the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-css-builder}{\texttt{com.liferay.css.builder}}
plugin to compile the \href{http://sass-lang.com/}{Sass} files in the
theme.
Since the plugin automatically resolves the Liferay Theme Builder
library as a dependency, you have to configure a repository that hosts
the library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-21}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildTheme} \textbar{} - \textbar{}
\hyperref[buildthemetask]{\texttt{BuildThemeTask}} \textbar{} Builds the
theme files.
The plugin also adds the following dependencies to tasks defined by the
\texttt{com.liferay.css.builder} and \texttt{war} plugins:
Name \textbar{} Depends On
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-css-builder\#tasks}{\texttt{buildCSS}}
\textbar{} \texttt{buildTheme}
\href{https://docs.gradle.org/current/userguide/war_plugin.html\#sec:war_default_settings}{\texttt{war}}
\textbar{} \texttt{buildTheme}
The \texttt{buildCSS} dependency compiles the Sass files contained in
the directory specified by the
\hyperref[outputdir]{\texttt{buildTheme.outputDir}} property. Moreover,
the \texttt{war} task is configured as follows
\begin{itemize}
\tightlist
\item
exclude the directory specified in the
\hyperref[diffsdir]{\texttt{buildTheme.diffsDir}} property from the
WAR file.
\item
include the files contained in the
\hyperref[outputdir]{\texttt{buildTheme.outputDir}} directory into the
WAR file.
\item
include only the compiled CSS files, not SCSS files, into the WAR
file.
\end{itemize}
The \texttt{buildTheme} task is automatically configured with sensible
defaults:
Property Name \textbar{} Default Value
\hyperref[diffsdir]{\texttt{diffsDir}} \textbar{}
\texttt{project.webAppDir} \hyperref[outputdir]{\texttt{outputDir}}
\textbar{} \texttt{"\$\{project.buildDir\}/buildTheme"}
\hyperref[parentfile]{\texttt{parentFile}} \textbar{} The first JAR file
in the \hyperref[parent-theme-dependencies]{\texttt{parentThemes}}
configuration that contains a
\texttt{META-INF/resources/\$\{buildTheme.parentName\}} directory, or
the first WAR file in the \texttt{parentThemes} configuration whose name
starts with \texttt{\$\{parentName\}-theme-}.
\hyperref[parentname]{\texttt{parentName}} \textbar{}
\texttt{"\_styled"}
\hyperref[templateextension]{\texttt{templateExtension}} \textbar{}
\texttt{"ftl"} \hyperref[themename]{\texttt{themeName}} \textbar{}
\texttt{project.name} \hyperref[unstyledfile]{\texttt{unstyledFile}}
\textbar{} The first JAR file in the
\hyperref[parent-theme-dependencies]{\texttt{parentThemes}}
configuration that contains a \texttt{META-INF/resources/\_unstyled}
directory.
\section{BuildThemeTask}\label{buildthemetask}
Tasks of type \texttt{BuildThemeTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.css.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} Theme Builder command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-theme-builder-dependency]{\texttt{project.configurations.themeBuilder}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{}
\texttt{"com.liferay.portal.tools.theme.builder.ThemeBuilder"}
\subsection{Task Properties}\label{task-properties-31}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{diffsDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory that contains the files to copy
over the parent theme. It sets the \texttt{-\/-diffs-dir} argument.
\texttt{outputDir} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} The directory where to build the theme. It sets the
\texttt{-\/-output-dir} argument. \texttt{parentDir} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} The directory of the
parent theme. It sets the \texttt{-\/-parent-path} argument.
\texttt{parentFile} \textbar{} \texttt{File} \textbar{} \texttt{null}
\textbar{} The JAR file of the parent theme. If \texttt{parentDir} is
specified, this property has no effect. It sets the
\texttt{-\/-parent-path} argument. \texttt{parentName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The name of the
parent theme. It sets the \texttt{-\/-parent-name} argument.
\texttt{templateExtension} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The extension of the template files, usually
\texttt{"ftl"} or \texttt{"vm"}. It sets the
\texttt{-\/-template-extension} argument. \texttt{themeName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The name of the new
theme. It sets the \texttt{-\/-name} argument. \texttt{unstyledDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
directory of
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-theme/frontend-theme-unstyled}{Liferay
Frontend Theme Unstyled}. It sets the \texttt{-\/-unstyled-dir}
argument. \texttt{unstyledFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The JAR file of
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-theme/frontend-theme-unstyled}{Liferay
Frontend Theme Unstyled}. If \texttt{unstyledDir} is specified, this
property has no effect. It sets the \texttt{-\/-unstyled-dir} argument.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.css.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\section{Additional Configuration}\label{additional-configuration-14}
There are additional configurations that can help you use the CSS
Builder.
\section{Liferay Theme Builder
Dependency}\label{liferay-theme-builder-dependency}
By default, the plugin creates a configuration called
\texttt{themeBuilder} and adds a dependency to the latest released
version of the Liferay Theme Builder. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{themeBuilder} configuration:
\begin{verbatim}
dependencies {
themeBuilder group: "com.liferay", name: "com.liferay.portal.tools.theme.builder", version: "1.1.7"
}
\end{verbatim}
\section{Parent Theme Dependencies}\label{parent-theme-dependencies}
By default, the plugin creates a configuration called
\texttt{parentThemes} and adds dependencies to the latest released
versions of the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-theme/frontend-theme-styled}{Liferay
Frontend Theme Styled},
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-theme/frontend-theme-unstyled}{Liferay
Frontend Theme Unstyled}, and
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-theme/frontend-theme-classic}{Liferay
Frontend Theme Classic} artifacts. It is possible to override this
setting and use a specific version of the artifacts by manually adding
dependencies to the \texttt{parentThemes} configuration. For example,
\begin{verbatim}
dependencies {
parentThemes group: "com.liferay", name: "com.liferay.frontend.theme.styled", version: "VERSION"
parentThemes group: "com.liferay", name: "com.liferay.frontend.theme.unstyled", version: "VERSION"
parentThemes group: "com.liferay.plugins", name: "classic-theme", version: "VERSION"
}
\end{verbatim}
Specifying dependency versions is not required when leveraging
workspace's
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{Target
Platform} functionality. All dependencies with the group ID
\texttt{com.liferay} or \texttt{com.liferay.portal} are automatically
set when targeting a platform. For external theme dependencies (e.g.,
\texttt{classic-theme} with the group ID \texttt{com.liferay.plugins}),
you can find the version used by your specific Liferay DXP instance by
leveraging the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell}. In a Gogo shell prompt, execute the following command:
\begin{verbatim}
lb -s theme
\end{verbatim}
This lists the deployed theme bundles and their versions. Extract the
versions for the theme dependencies you want to leverage and add them to
your configuration.
\chapter{TLD Formatter Gradle Plugin}\label{tld-formatter-gradle-plugin}
The TLD Formatter Gradle plugin lets you format a project's TLD files
using the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/tld-formatter}{Liferay
TLD Formatter} tool.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-23}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.tld.formatter", version: "1.0.9"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.tld.formatter"
\end{verbatim}
Since the plugin automatically resolves the Liferay TLD Formatter
library as a dependency, you have to configure a repository that hosts
the library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-22}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{formatTLD} \textbar{} - \textbar{}
\hyperref[formattldtask]{\texttt{FormatTLDTask}} \textbar{} Runs the
Liferay TLD Formatter to format files.
\section{FormatTLDTask}\label{formattldtask}
Tasks of type \texttt{FormatTLDTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} TLD Formatter command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-tld-formatter-dependency]{\texttt{project.configurations.tldFormatter}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.tld.formatter.TLDFormatter"}
\subsection{Task Properties}\label{task-properties-32}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{plugin} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to format all the TLD files contained
in the
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:workingDir}{\texttt{workingDir}}
directory. If \texttt{false}, all \texttt{liferay-portlet-ext.tld} files
are ignored. It sets the \texttt{tld.plugin} argument.
\section{Additional Configuration}\label{additional-configuration-15}
There are additional configurations that can help you use the TLD
Formatter.
\section{Liferay TLD Formatter
Dependency}\label{liferay-tld-formatter-dependency}
By default, the plugin creates a configuration called
\texttt{tldFormatter} and adds a dependency to the latest released
version of Liferay TLD Formatter. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{tldFormatter} configuration:
\begin{verbatim}
dependencies {
tldFormatter group: "com.liferay", name: "com.liferay.tld.formatter", version: "1.0.5"
}
\end{verbatim}
\chapter{TLDDoc Builder Gradle
Plugin}\label{tlddoc-builder-gradle-plugin}
The TLDDoc Builder Gradle plugin lets you run the
\href{http://web.archive.org/web/20070624180825/https://taglibrarydoc.dev.java.net/}{Tag
Library Documentation Generator} tool in order to generate documentation
for the JSP Tag Library Descriptor (TLD) files in your project.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-24}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.tlddoc.builder", version: "1.3.3"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
\end{verbatim}
There are two TLDDoc Builder Gradle plugins you can apply to your
project:
\begin{itemize}
\item
Apply the \hyperref[tlddoc-builder-plugin]{\emph{TLDDoc Builder
Plugin}} to generate tag library documentation for your project:
\begin{verbatim}
apply plugin: "com.liferay.tlddoc.builder"
\end{verbatim}
\item
Apply the \hyperref[app-tlddoc-builder-plugin]{\emph{App TLDDoc
Builder Plugin}} in a parent project to generate the tag library
documentation as a single, combined HTML document for an application
that spans different subprojects, each one representing a different
component of the same application:
\begin{verbatim}
apply plugin: "com.liferay.app.tlddoc.builder"
\end{verbatim}
\end{itemize}
Since the plugin automatically resolves the Tag Library Documentation
Generator library as a dependency, you must configure a repository that
hosts the library and its transitive dependencies. The Liferay CDN
repository hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{TLDDoc Builder Plugin}\label{tlddoc-builder-plugin}
The plugin adds three tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{copyTLDDocResources} \textbar{} - \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html}{\texttt{Copy}}
\textbar{} Copies the tag library documentation resources from
\texttt{src/main/tlddoc} to the \hyperref[destinationdir]{destination
directory} of the \texttt{tlddoc} task. \texttt{tlddoc} \textbar{}
\texttt{copyTLDDocResources}, \texttt{validateTLD} \textbar{}
\hyperref[tlddoctask]{\texttt{TLDDocTask}} \textbar{} Generates the tag
library documentation. \texttt{validateTLD} \textbar{} - \textbar{}
\hyperref[validateschematask]{\texttt{ValidateSchemaTask}} \textbar{}
Validates the TLD files in the project.
The \texttt{tlddoc} task is automatically configured with sensible
defaults, depending on whether the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied:
Property Name \textbar{} Default Value with the \texttt{java} plugin
\hyperref[destinationdir]{\texttt{destinationDir}} \textbar{}
\texttt{\$\{project.docsDir\}/tlddoc}
\hyperref[includes]{\texttt{includes}} \textbar{}
\texttt{{[}"**/*.tld"{]}} \hyperref[source]{\texttt{source}} \textbar{}
\texttt{project.sourceSets.main.resources.srcDirs}
The \texttt{validateTLD} task is also automatically configured with
sensible defaults, depending on whether the \texttt{java} plugin is
applied:
Property Name \textbar{} Default Value \texttt{includes} \textbar{}
\textbf{If the \texttt{java} plugin is applied:}
\texttt{{[}"**/*.tld"{]}}
\textbf{Otherwise:} \texttt{{[}{]}}
\texttt{source} \textbar{}
\textbf{If the \texttt{java} plugin is applied:}
\texttt{project.sourceSets.main.resources.srcDirs}
\textbf{Otherwise:} \texttt{null}
By default, the \texttt{tlddoc} task generates the documentation for all
the TLD files that are found in the resources directories of the
\texttt{main} source set. The documentation files are saved in
\texttt{build/docs/tlddoc} and include the files copied from
\texttt{src/main/tlddoc}.
The \texttt{copyTLDDocResources} task lets you add references to images
and other resources directly in the TLD files. For example, if the
project includes an image called \texttt{breadcrumb.png} in the
\texttt{src/main/tlddoc/images} directory, you can reference it in a TLD
file contained in the \texttt{src/main/resources} directory:
\begin{verbatim}
Hello World
\end{verbatim}
\section{App TLDDoc Builder Plugin}\label{app-tlddoc-builder-plugin}
In order to use the App TLDDoc Builder plugin, it is required to apply
the \texttt{com.liferay.app.tlddoc.builder} plugin in a parent project
(that is, a project that is a common ancestor of all the subprojects
representing the various components of the app). It is also required to
apply the
\hyperref[tlddoc-builder-plugin]{\texttt{com.liferay.tlddoc.builder}}
plugin to all the subprojects that contain TLD files.
The App TLDDoc Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/standard_plugins.html\#N135C1}{\texttt{base}}
plugin. It also adds three tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{appTLDDoc} \textbar{} \texttt{copyAppTLDDocResources}, the
\hyperref[validatetld]{\texttt{validateTLD}} tasks of the subprojects
\textbar{} \hyperref[tlddoctask]{\texttt{TLDDocTask}} \textbar{}
Generates tag library documentation for the app.
\texttt{copyAppTLDDocResources} \textbar{} - \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html}{\texttt{Copy}}
\textbar{} Copies the tag library documentation resources defined as
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html\#getFiles()}{inputs}
for the \hyperref[copytlddocresources]{\texttt{copyTDLDocResources}}
tasks of the subprojects, aggregating them into the
\hyperref[destinationdir]{destination directory} of the
\texttt{appTLDDoc} task. \texttt{jarAppTLDDoc} \textbar{}
\texttt{appTLDDoc} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html}{\texttt{Jar}}
\textbar{} Assembles a JAR archive containing the tag library
documentation files for this app.
The \texttt{appTLDDoc} task is automatically configured with sensible
defaults:
Property Name \textbar{} Default Value
\hyperref[destinationdir]{\texttt{destinationDir}} \textbar{}
\texttt{\$\{project.buildDir\}/docs/tlddoc}
\hyperref[source]{\texttt{source}} \textbar{} The sum of all the
\texttt{tlddoc.source} values of the subprojects
\section{Project Extension}\label{project-extension-8}
The App TLDDoc Builder plugin exposes the following properties through
the extension named \texttt{appTLDDocBuilder}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{subprojects} \textbar{}
\texttt{Set\textless{}Project\textgreater{}} \textbar{}
\texttt{project.subprojects} \textbar{} The subprojects to include in
the tag library documentation of the app.
The same extension exposes the following methods:
Method \textbar{} Description
\texttt{AppTLDDocBuilderExtension\ subprojects(Iterable\textless{}Project\textgreater{}\ subprojects)}
\textbar{} Include additional projects in the tag library documentation
of the app.
\texttt{AppTLDDocBuilderExtension\ subprojects(Project...\ subprojects)}
\textbar{} Include additional projects in the tag library documentation
of the app.
\section{Tasks}\label{tasks-23}
\section{TLDDocTask}\label{tlddoctask}
Tasks of type \texttt{TLDDocTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.tlddoc.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} Tag Library Documentation Generator command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[tag-library-documentation-generator-dependency]{\texttt{project.configurations.tlddoc}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.sun.tlddoc.TLDDoc"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}}
\textbar{} \texttt{"256m"}
The \texttt{TLDDocTask} class is also very similar to
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}},
which means it provides a \texttt{source} property and lets you specify
include and exclude patterns.
\subsection{Task Properties}\label{task-properties-33}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{destinationDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory where the tag library
documentation files are saved. \texttt{excludes} \textbar{}
\texttt{Set\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The TLD file patterns to exclude. \texttt{includes}
\textbar{} \texttt{Set\textless{}String\textgreater{}} \textbar{}
\texttt{{[}{]}} \textbar{} The TLD file patterns to include.
\texttt{source} \textbar{}
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileTree.html}{\texttt{FileTree}}
\textbar{} \texttt{{[}{]}} \textbar{} The TLD files to generate
documentation for, after the include and exclude patterns have been
applied. \texttt{xsltDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The directory that contains the custom XSLT
stylesheets used by the Tag Library Documentation Generator to produce
the final documentation files. It sets the \texttt{-xslt} argument.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.tlddoc.Object)}{\texttt{project.file}}.
\subsection{Task Methods}\label{task-methods-11}
The methods available for \texttt{TLDDocTask} are exactly the same as
the one defined in the
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}}
class.
\section{ValidateSchemaTask}\label{validateschematask}
Tasks of type \texttt{ValidateSchemaTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:include(java.lang.Iterable)}{\texttt{include}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:exclude(java.lang.Iterable)}{\texttt{exclude}},
are available.
Tasks of this type invoke the
\href{http://ant.apache.org/manual/Tasks/schemavalidate.html}{\texttt{schemavalidate}}
Ant task in order to validate XML files described by an XML schema.
\subsection{Task Properties}\label{task-properties-34}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{dtdDisabled} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to disable DTD support.
\texttt{fullChecking} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to enable full schema checking.
\texttt{lenient} \textbar{} \texttt{boolean} \textbar{} \texttt{false}
\textbar{} Whether to only check if the XML document is well-formed.
\texttt{xmlParserClassName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The class name of the XML parser to use.
\texttt{xmlParserClasspath} \textbar{} \texttt{FileCollection}
\textbar{} \texttt{null} \textbar{} The classpath with the XML parser.
It is possible to use Closures and Callables as values for the
\texttt{String} properties to defer evaluation until task execution.
\section{Additional Configuration}\label{additional-configuration-16}
There are additional configurations that can help you use the TLDDoc
Builder.
\section{Tag Library Documentation Generator
Dependency}\label{tag-library-documentation-generator-dependency}
By default, the plugin creates a configuration called \texttt{tlddoc}
and adds a dependency to the 1.3 version of the Tag Library
Documentation Generator. It is possible to override this setting and use
a specific version of the tool by manually adding a dependency to the
\texttt{tlddoc} configuration:
\begin{verbatim}
dependencies {
tlddoc group: "taglibrarydoc", name: "tlddoc", version: "1.3"
}
\end{verbatim}
\chapter{Whip Gradle Plugin}\label{whip-gradle-plugin}
The Whip Gradle plugin lets you use the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/test/whip}{Liferay
Whip} library to ensure that unit tests fully cover your project's code.
See
\href{https://github.com/liferay/liferay-portal/tree/master/modules/sdk/gradle-plugins-whip/src/gradleTest/smoke}{here}
for a usage sample.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-25}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.whip", version: "1.0.7"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.whip"
\end{verbatim}
Since the plugin automatically resolves the Liferay Whip library as a
dependency, you have to configure a repository that hosts the library
and its transitive dependencies. The Liferay CDN repository hosts them
all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
By default, Whip is automatically applied to all tasks of type
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/Test.html}{\texttt{Test}}.
If a task has Whip applied and Whip is \hyperref[enabled]{enabled}, then
Whip is configured as a Java Agent.
\section{Project Extension}\label{project-extension-9}
The Whip Gradle plugin exposes the following properties through the
extension named \texttt{whip}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{version} \textbar{} \texttt{String} \textbar{}
\texttt{latest.release} \textbar{} The version of the Liferay Whip
library to use.
The same extension exposes the following methods:
Method \textbar{} Description \texttt{void\ applyTo(Task\ task)}
\textbar{} Applies Whip to a task. The task instance must implement the
\href{https://docs.gradle.org/current/javadoc/org/gradle/process/JavaForkOptions.html}{\texttt{JavaForkOptions}}
interface.
\section{Task Extension}\label{task-extension}
If Whip is applied, the following task properties are available through
the extension named \texttt{whip}:
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{dataFile} \textbar{} \texttt{File} \textbar{}
\texttt{test-coverage/whip.dat} \textbar{} \texttt{enabled} \textbar{}
\texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether to
configure Whip as a Java Agent. \texttt{excludes} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The class name patterns to exclude when checking for unit
test code coverage. For example, a value could be
\texttt{{[}\textquotesingle{}.*Test\textquotesingle{},\ \textquotesingle{}.*Test\textbackslash{}\textbackslash{}\$.*\textquotesingle{},\ \textquotesingle{}.*\textbackslash{}\textbackslash{}\$Proxy.*\textquotesingle{},\ \textquotesingle{}com/liferay/whip/.*\textquotesingle{}{]}}.
\texttt{includes} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \texttt{{[}{]}}
\textbar{} The class name patterns to include when checking for unit
test code coverage. \texttt{instrumentDump} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} \texttt{whipJarFile} \textbar{}
\texttt{File} \textbar{} The first file in the \texttt{whip}
configuration whose name starts with \texttt{com.liferay.whip-}.
\textbar{} The Whip JAR file.
The same extension exposes the following methods:
Method \textbar{} Description
\texttt{WhipTaskExtension\ excludes(Iterable\textless{}Object\textgreater{}\ excludes)}
\textbar{} Adds class name patterns to exclude when checking for unit
test coverage. \texttt{WhipTaskExtension\ excludes(Object...\ excludes)}
\textbar{} Adds class name patterns to exclude when checking for unit
test coverage.
\texttt{WhipTaskExtension\ includes(Iterable\textless{}Object\textgreater{}\ includes)}
\textbar{} Adds class name patterns to include when checking for unit
test coverage. \texttt{WhipTaskExtension\ includes(Object...\ includes)}
\textbar{} Adds class name patterns to include when checking for unit
test coverage.
\section{Additional Configuration}\label{additional-configuration-17}
There are additional configurations that can help you use Whip.
\section{Liferay Whip Dependency}\label{liferay-whip-dependency}
By default, the Whip Gradle plugin creates a configuration called
\texttt{whip} and adds a dependency to the version of Liferay Whip
configured in the \hyperref[version]{\texttt{whip.version}} extension
property. It is possible to override this setting and use a specific
version of the library by manually adding a dependency to the
\texttt{whip} configuration:
\begin{verbatim}
dependencies {
whip group: "com.liferay", name: "com.liferay.whip", version: "1.0.1"
}
\end{verbatim}
In order to leverage the sensible default of the
\hyperref[whipjarfile]{\texttt{whip.whipJarFile}} task property, the
name of the dependency must be \texttt{com.liferay.whip}. Otherwise, it
will be necessary to set the value of the \texttt{whip.whipJarFile}
property manually.
\chapter{WSDD Builder Gradle Plugin}\label{wsdd-builder-gradle-plugin}
The WSDD Builder Gradle plugin lets you run the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-wsdd-builder}{Liferay
WSDD Builder} tool to generate the
\href{http://axis.apache.org/axis/}{Apache Axis} Web Service Deployment
Descriptor (WSDD) files from a
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} \texttt{service.xml} file.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-26}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.wsdd.builder", version: "1.0.13"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.portal.tools.wsdd.builder"
\end{verbatim}
The WSDD Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin.
Since the plugin automatically resolves the Liferay WSDD Builder library
as a dependency, you have to configure a repository that hosts the
library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-24}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildWSDD} \textbar{}
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#sec:compile}{\texttt{compileJava}}
\textbar{} \hyperref[buildwsddtask]{\texttt{BuildWSDDTask}} \textbar{}
Runs the Liferay WSDD Builder.
By default, the \texttt{buildWSDD} task uses the
\texttt{\$\{project.projectDir\}/service.xml} file as input. Then, it
generates \texttt{\$\{project.projectDir\}/server-config.wsdd} and the
\texttt{*\_deploy.wsdd} and \texttt{*\_undeploy.wsdd} files in the first
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceSet.html\#org.gradle.api.tasks.SourceSet:resources}{\texttt{resources}}
directory of the \texttt{main}
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#N1503E}{source
set} (by default: \texttt{src/main/resources}).
If the
\href{https://docs.gradle.org/current/userguide/war_plugin.html}{\texttt{war}}
plugin is applied, the task uses
\texttt{\$\{project.webAppDir\}/WEB-INF/service.xml} as input to
generate \texttt{\$\{project.webAppDir\}/WEB-INF/server-config.wsdd}.
The \texttt{*\_deploy.wsdd} and \texttt{*\_undeploy.wsdd} files are
still generated in the first \texttt{resources} directory of the
\texttt{main} source set.
Liferay WSDD Build Service requires an additional classpath (configured
with the \texttt{buildWSDD.builderClasspath} property), to correctly
generate the WSDD files. The \texttt{buildWSDD} task uses the following
default value, which creates an implicit dependency to the
\texttt{compileJava} task:
\begin{verbatim}
tasks.compileJava.outputs.files + sourceSets.main.compileClasspath + sourceSets.main.runtimeClasspath
\end{verbatim}
\section{BuildWSDDTask}\label{buildwsddtask}
Tasks of type \texttt{BuildWSDDTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args(java.lang.Iterable)}{\texttt{args}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:maxHeapSize}{\texttt{maxHeapSize}},
are available. They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:args}{\texttt{args}}
\textbar{} WSDD Builder command line arguments
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:classpath}{\texttt{classpath}}
\textbar{}
\hyperref[liferay-wsdd-builder-dependency]{\texttt{project.configurations.wsddBuilder}}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html\#org.gradle.api.tasks.JavaExec:main}{\texttt{main}}
\textbar{} \texttt{"com.liferay.portal.tools.wsdd.builder.WSDDBuilder"}
\subsection{Task Properties}\label{task-properties-35}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{builderClasspath} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} A classpath that the Liferay WSDD
Builder uses to generate WSDD files. It sets the
\texttt{wsdd.class.path} argument. \texttt{inputFile} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} A \texttt{service.xml}
from which to generate the WSDD files. It sets the
\texttt{wsdd.input.file} argument. \texttt{outputDir} \textbar{}
\texttt{File} \textbar{} \texttt{null} \textbar{} A directory where the
\texttt{*\_deploy.wsdd} and \texttt{*\_undeploy.wsdd} files are
generated. It sets the \texttt{wsdd.output.path} argument.
\texttt{serverConfigFile} \textbar{} \texttt{File} \textbar{}
\texttt{\$\{project.projectDir\}/server-config.wsdd} \textbar{} A
\texttt{server-config.wsdd} file to generate. It sets the
\texttt{wsdd.server.config.file} argument. \texttt{serviceNamespace}
\textbar{} \texttt{String} \textbar{} \texttt{"Plugin"} \textbar{} A
namespace for the WSDD Service. It sets the
\texttt{wsdd.service.namespace} argument.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
Moreover, it is possible to use Closures and Callables as values for the
\texttt{String} properties, to defer evaluation until task execution.
\section{Additional Configuration}\label{additional-configuration-18}
There are additional configurations that can help you use the WSDD
Builder.
\section{Liferay WSDD Builder
Dependency}\label{liferay-wsdd-builder-dependency}
By default, the plugin creates a configuration called
\texttt{wsddBuilder} and adds a dependency to the latest released
version of the Liferay WSDD Builder. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{wsddBuilder} configuration:
\begin{verbatim}
dependencies {
wsddBuilder group: "com.liferay", name: "com.liferay.portal.tools.wsdd.builder", version: "1.0.10"
}
\end{verbatim}
\chapter{WSDL Builder Gradle Plugin}\label{wsdl-builder-gradle-plugin}
The WSDL Builder Gradle plugin lets you generate
\href{http://axis.apache.org/axis/}{Apache Axis} client stubs from Web
Service Description (WSDL) files.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-27}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.wsdl.builder", version: "2.0.3"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.wsdl.builder"
\end{verbatim}
The WSDL Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin.
Since the plugin automatically resolves the Apache Axis library as a
dependency, you have to configure a repository that hosts the library
and its transitive dependencies. The Liferay CDN repository hosts them
all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-25}
The plugin adds one main task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildWSDL} \textbar{} - \textbar{}
\hyperref[buildwsdltask]{\texttt{BuildWSDLTask}} \textbar{} Generates
WSDL client stubs.
By default, the \texttt{buildWSDL} task looks for WSDL files in the
\texttt{\$\{project.projectDir\}/wsdl} directory. If the
\href{https://docs.gradle.org/current/userguide/war_plugin.html}{\texttt{war}}
plugin is applied, it looks in the
\texttt{\$\{project.webAppDir\}/WEB-INF/wsdl} directory.
For each WSDL file that can be found, the task generates client stubs
via direct invocation of the
\href{http://axis.apache.org/axis/java/user-guide.html\#Client-side_bindings}{\emph{WSDL2Java}}
tool, saving them in the first
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceSet.html\#org.gradle.api.tasks.SourceSet:java}{\texttt{java}}
directory of the \texttt{main}
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#N1503E}{source
set} (by default: \texttt{src/main/java}).
If configured to do so, \texttt{buildWSDL} can instead save the client
stub Java files in a temporary directory, compile them, and package them
in JAR files. The JAR files are named after the WSDL file and saved in
\texttt{\$\{project.projectDir\}/lib}, by default, or in
\texttt{\$\{project.webAppDir\}/WEB-INF/lib}, if the \texttt{war} plugin
is applied.
\section{BuildWSDLTask}\label{buildwsdltask}
Tasks of type \texttt{FormatWSDLTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:include(java.lang.Iterable)}{\texttt{include}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:exclude(java.lang.Iterable)}{\texttt{exclude}},
are available.
\subsection{Task Properties}\label{task-properties-36}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{buildLibs} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to package the client stub classes of
each WSDL file in JAR files, saved to the directory the
\texttt{destinationDir} property references. If \texttt{false}, the task
generates the client stub Java files to the \texttt{destinationDir}
directory. \texttt{destinationDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} A directory where the client stub Java files
(if \texttt{buildLibs} is \texttt{false}) or the client stub JAR files
(if \texttt{buildLibs} is \texttt{true}) are saved.
\texttt{generateOptions.mapping} \textbar{} \texttt{Map} \textbar{}
\texttt{{[}:{]}} \textbar{} Namespace-to-package mappings (sets the
\texttt{-\/-NStoPkg} argument in the \emph{WSDL2Java} invocation). It is
possible to use a \texttt{Closure} or a \texttt{Callable}, to defer
evaluation until task execution.. \texttt{generateOptions.noWrapped}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to turn off support for ``wrapped'' document/literal (sets the
\texttt{-\/-noWrapped} argument in the \emph{WSDL2Java} invocation).
\texttt{generateOptions.serverSide} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to emit server-side
bindings for the web service (sets the \texttt{-\/-server-side} argument
in the \emph{WSDL2Java} invocation). \texttt{generateOptions.verbose}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to print informational messages (sets the \texttt{-\/-verbose} argument
in the \emph{WSDL2Java} invocation). \texttt{includeSource} \textbar{}
\texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether to package
the client stub Java files in the JAR file's \texttt{OSGI-OPT/src}
directory. If \texttt{buildLibs} is \texttt{false}, this property has no
effect. \texttt{includeWSDLs} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to configure the
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#sec:resources}{\texttt{processResources}}
task to include the WSDL files in the project JAR's \texttt{wsdl}
directory.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
\subsection{Task Methods}\label{task-methods-12}
Method Signature \textbar{} Description
\texttt{generateOptions.mapping(Object\ namespace,\ Object\ packageName)}
\textbar{} Adds a namespace-to-package mapping.
\texttt{generateOptions.mappings(Map\ mappings)} \textbar{} Adds
multiple namespace-to-package mappings.
\subsection{Helper Tasks}\label{helper-tasks-1}
At the end of the
\href{https://docs.gradle.org/current/userguide/build_lifecycle.html\#N11BAE}{project
evaluation}, a series of helper tasks are created for each WSDL file
returned by the
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:source}{\texttt{source}}
property of the \texttt{BuildWSDLTask} tasks. The names of the helper
tasks start with the WSDL file name, without any extension.
\begin{itemize}
\tightlist
\item
\texttt{\$\{WSDL\ file\ title\}Generate} of type
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}}:
invokes
\href{https://axis.apache.org/axis/java/reference.html\#WSDL2Java_Reference}{\emph{WSDL2Java}}
to generate the client stubs for the WSDL file.
\end{itemize}
If \texttt{buildWSDLTask.buildLibs} is \texttt{true}, the following
helper tasks are also created:
\begin{itemize}
\tightlist
\item
\texttt{\$\{WSDL\ file\ title\}Compile} of type
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html}{\texttt{JavaCompile}}:
compiles the client stub Java files for the WSDL file.
\item
\texttt{\$\{WSDL\ file\ title\}Jar} of type
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html}{\texttt{Jar}}:
packages in a JAR file called \texttt{\$\{WSDL\ file\ title\}-ws.jar},
the client stub for the WSDL file.
\end{itemize}
\section{Additional Configuration}\label{additional-configuration-19}
There are additional configurations that can help you use WSDL Builder.
\section{Apache Axis Dependency}\label{apache-axis-dependency}
By default, the plugin creates a configuration called
\texttt{wsdlBuilder} and adds the following dependencies:
\begin{itemize}
\tightlist
\item
\texttt{axis:axis-wsdl4j:1.5.1}
\item
\texttt{com.liferay:org.apache.axis:1.4.LIFERAY-PATCHED-1}
\item
\texttt{commons-discovery:commons-discovery:0.2}
\item
\texttt{commons-logging:commons-logging:1.0.4}
\item
\texttt{javax.activation:activation:1.1}
\item
\texttt{javax.mail:mail:1.4}
\item
\texttt{org.apache.axis:axis-jaxrpc:1.4}
\item
\texttt{org.apache.axis:axis-saaj:1.4}
\end{itemize}
It is possible to override this setting and use a specific version of
Apache Axis, by manually populating the \texttt{wsdlBuilder}
configuration with the desired dependencies.
\chapter{XML Formatter Gradle Plugin}\label{xml-formatter-gradle-plugin}
The XML Formatter Gradle plugin lets you format a project's XML files
using the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/xml-formatter}{Liferay
XML Formatter} tool.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-28}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.xml.formatter", version: "1.0.11"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.xml.formatter"
\end{verbatim}
Since the plugin automatically resolves the Liferay XML Formatter
library as a dependency, you have to configure a repository that hosts
the library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-26}
The plugin adds one task to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{formatXML} \textbar{} - \textbar{}
\hyperref[formatxmltask]{\texttt{FormatXMLTask}} \textbar{} Runs the
Liferay XML Formatter to format the project files.
If the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin is applied, the task formats XML files contained in the
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceSet.html\#org.gradle.api.tasks.SourceSet:resources}{\texttt{resources}}
directories of the \texttt{main}
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#N1503E}{source
set} (by default: \texttt{src/main/resources/**/*.xml}).
\section{FormatXMLTask}\label{formatxmltask}
Tasks of type \texttt{FormatXMLTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html}{\texttt{SourceTask}},
so all its properties and methods, such as
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:include(java.lang.Iterable)}{\texttt{include}}
and
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceTask.html\#org.gradle.api.tasks.SourceTask:exclude(java.lang.Iterable)}{\texttt{exclude}},
are available.
\subsection{Task Properties}\label{task-properties-37}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{classpath} \textbar{}
\href{https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileCollection.html}{\texttt{FileCollection}}
\textbar{}
\hyperref[liferay-xml-formatter-dependency]{\texttt{project.configurations.xmlFormatter}}
\textbar{} The classpath for executing the main class.
\texttt{mainClassName} \textbar{} \texttt{String} \textbar{}
\texttt{"com.liferay.xml.formatter.XMLFormatter"} \textbar{} The fully
qualified name of the XML Formatter Main class. \texttt{stripComments}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to remove all the comments from the XML files. It sets the
\texttt{xml.formatter.strip.comments} argument.
\section{Additional Configuration}\label{additional-configuration-20}
There are additional configurations that can help you use the XML
Formatter.
\section{Liferay XML Formatter
Dependency}\label{liferay-xml-formatter-dependency}
By default, the plugin creates a configuration called
\texttt{xmlFormatter} and adds a dependency to the latest released
version of the Liferay XML Formatter. It is possible to override this
setting and use a specific version of the tool by manually adding a
dependency to the \texttt{xmlFormatter} configuration:
\begin{verbatim}
dependencies {
xmlFormatter group: "com.liferay", name: "com.liferay.xml.formatter", version: "1.0.5"
}
\end{verbatim}
\chapter{XSD Builder Gradle Plugin}\label{xsd-builder-gradle-plugin}
The XSD Builder Gradle plugin lets you generate
\href{https://xmlbeans.apache.org/}{Apache XMLBeans} bindings from XML
Schema (XSD) files.
The plugin has been successfully tested with Gradle 4.10.2.
\section{Usage}\label{usage-29}
To use the plugin, include it in your build script:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.xsd.builder", version: "1.0.7"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.xsd.builder"
\end{verbatim}
The XSD Builder plugin automatically applies the
\href{https://docs.gradle.org/current/userguide/java_plugin.html}{\texttt{java}}
plugin.
Since the plugin automatically resolves the Liferay Service Builder
library as a dependency, you have to configure a repository that hosts
the library and its transitive dependencies. The Liferay CDN repository
hosts them all:
\begin{verbatim}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
\end{verbatim}
\section{Tasks}\label{tasks-27}
The plugin adds three tasks to your project:
Name \textbar{} Depends On \textbar{} Type \textbar{} Description
\texttt{buildXSD} \textbar{} \texttt{buildXSDCompile} \textbar{}
\hyperref[buildxsdtask]{\texttt{BuildXSDTask}} \textbar{} Generates
XMLBeans bindings and compiles them in a JAR file.
\texttt{buildXSDGenerate} \textbar{} \texttt{cleanBuildXSDGenerate}
\textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html}{\texttt{JavaExec}}
\textbar{} Invokes the
\href{https://xmlbeans.apache.org/docs/2.6.0/guide/tools.html\#scomp}{XMLBeans
Schema Compiler} to generate Java types from XML Schema.
\texttt{buildXSDCompile} \textbar{} \texttt{buildXSDGenerate},
\texttt{cleanBuildXSDCompile} \textbar{}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html}{\texttt{JavaCompile}}
\textbar{} Compiles the generated Java types.
By default, the \texttt{buildXSD} task looks for XSD files in the
\texttt{\$\{project.projectDir\}/xsd} directory, and saves the generated
JAR file as
\texttt{\$\{project.projectDir\}/lib/\$\{project.archivesBaseName\}-xbean.jar}.
If the
\href{https://docs.gradle.org/current/userguide/war_plugin.html}{\texttt{war}}
plugin is applied, the task looks for XSD files in the
\texttt{\$\{project.webAppDir\}/WEB-INF/xsd} directory, and saves the
generated JAR file as
\texttt{\$\{project.webAppDir\}/WEB-INF/lib/\$\{project.archivesBaseName\}-xbean.jar}.
\section{BuildXSDTask}\label{buildxsdtask}
Tasks of type \texttt{BuildXSDTask} extend
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html}{\texttt{Zip}}.
They also have the following properties set by default:
Property Name \textbar{} Default Value
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html\#org.gradle.api.tasks.bundling.Zip:appendix}{\texttt{appendix}}
\textbar{} \texttt{"xbean"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html\#org.gradle.api.tasks.bundling.Zip:extension}{\texttt{extension}}
\textbar{} \texttt{"jar"}
\href{https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Zip.html\#org.gradle.api.tasks.bundling.Zip:version}{\texttt{version}}
\textbar{} \texttt{null}
For each task of type \texttt{BuildXSDTask}, the following helper tasks
are created:
\begin{itemize}
\tightlist
\item
\texttt{\$\{buildXSDTask.name\}Compile}
\item
\texttt{\$\{buildXSDTask.name\}Generate}
\end{itemize}
\subsection{Task Properties}\label{task-properties-38}
Property Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{inputDir} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} A directory containing XSD files from which to
generate \href{https://xmlbeans.apache.org/}{Apache XMLBeans} bindings.
The properties of type \texttt{File} support any type that can be
resolved by
\href{https://docs.gradle.org/current/dsl/org.gradle.api.Project.html\#org.gradle.api.Project:file(java.lang.Object)}{\texttt{project.file}}.
\section{Additional Configuration}\label{additional-configuration-21}
There are additional configurations that can help you use the XSD
Builder.
\section{Apache XMLBeans Dependency}\label{apache-xmlbeans-dependency}
By default, the XSD Builder Gradle plugin creates a configuration called
\texttt{xsdBuilder} and adds a dependency to the 2.5.0 version of Apache
XMLBeans. It is possible to override this setting and use a specific
version of the library by manually adding a dependency to the
\texttt{xsdBuilder} configuration:
\begin{verbatim}
dependencies {
xsdBuilder group: "org.apache.xmlbeans", name: "xmlbeans", version: "2.6.0"
}
\end{verbatim}
\chapter{Liferay Faces}\label{liferay-faces}
Liferay Faces is an umbrella project that provides support for the
JavaServer™ Faces (JSF) standard within Liferay DXP. It encompasses the
following projects:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-bridge}{Liferay
Faces Bridge} enables you to deploy JSF web apps as portlets without
writing portlet-specific Java code. It also contains innovative
features that make it possible to leverage the power of JSF 2.x inside
a portlet application. Liferay Faces Bridge implements the JSR 329
Portlet Bridge Standard.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-alloy}{Liferay
Faces Alloy} enables you to use AlloyUI components in a way that is
consistent with JSF development.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-portal}{Liferay
Faces Portal} enables you to leverage Liferay-specific utilities and
UI components in JSF portlets.
\end{itemize}
In this section of reference documentation, you'll learn more about each
of these projects. You'll also learn about the Liferay Faces version
scheme.
\chapter{Liferay Faces Version
Scheme}\label{liferay-faces-version-scheme}
In this article, you'll learn which Liferay Faces artifacts should be
used with your portlet and explore the Liferay Faces versioning scheme
by discovering what each component of a version means. Once you have the
versioning scheme mastered, you can view several example configurations.
\section{Using The Liferay Faces Archetype
Portlet}\label{using-the-liferay-faces-archetype-portlet}
The \href{http://liferayfaces.org}{Liferay Faces Archetype portlet} can
be used to determine the Liferay Faces artifacts and versions that you
must include in your portlet. Select your preferred Liferay Portal
version, JSF version, component suite (optional), and build tool, and
the portlet will provide you with both a command to generate your
portlet from a Maven archetype and a list of dependencies that can be
copied into your build files. In the next section, you'll be provided
with compatibility information about each version of the Liferay Faces
artifacts.
\section{Liferay Faces Alloy}\label{liferay-faces-alloy}
Provides a suite of JSF components that utilize
\href{http://alloyui.com/}{AlloyUI}.
\noindent\hrulefill
Branch\textbar Example Artifact\textbar AlloyUI\textbar JSF
API\textbar Additional Info\textbar{}
\href{https://github.com/liferay/liferay-faces-alloy/tree/master}{master
(4.x)}\textbar com.liferay.faces.alloy-4.1.0.jar\textbar3.1.x\textbar2.2+\textbar{}\emph{AlloyUI
3.1.x is the version that comes bundled with Liferay Portal
7.3.}\textbar{}
\href{https://github.com/liferay/liferay-faces-alloy/tree/3.x}{3.x}\textbar com.liferay.faces.alloy-3.1.0.jar\textbar3.0.x\textbar2.2+\textbar{}\emph{AlloyUI
3.0.x is the version that comes bundled with Liferay Portal
7.0/7.1/7.2.}\textbar{}
\href{https://github.com/liferay/liferay-faces-alloy/tree/2.x}{2.x}\textbar com.liferay.faces.alloy-2.0.1.jar\textbar2.0.x\textbar2.1+\textbar{}\emph{AlloyUI
2.0.x is the version that comes bundled with Liferay Portal
6.2.}\textbar{}
\href{https://github.com/liferay/liferay-faces-alloy/tree/1.x}{1.x}\textbar com.liferay.faces.alloy-1.0.1.jar\textbar2.0.x\textbar1.2\textbar{}\emph{AlloyUI
2.0.x is the version that comes bundled with Liferay Portal
6.2.}\textbar{}
\noindent\hrulefill
\section{Liferay Faces Bridge}\label{liferay-faces-bridge}
Provides the ability to deploy JSF web applications as portlets within
\href{https://portals.apache.org/pluto/}{Apache Pluto}, the reference
implementation for JSR 286 (Portlet 2.0) and JSR 362 (Portlet 3.0).
\noindent\hrulefill
Branch\textbar Example Artifacts\textbar Portlet API\textbar JSF
API\textbar JCP Specification\textbar Additional Info\textbar{} API:
\href{https://github.com/liferay/liferay-faces-bridge-api/tree/5.x}{5.x}IMPL:
\href{https://github.com/liferay/liferay-faces-bridge-impl/tree/5.x}{5.x}\textbar com.liferay.faces.bridge.api-5.0.0.jarcom.liferay.faces.bridge.impl-5.0.0.jar\textbar3.0\textbar2.2\textbar{}\href{https://www.jcp.org/en/jsr/detail?id=378}{JSR
378}\textbar{}\emph{Under ``Final Review'' by the JCP and scheduled for
release in 2020.}\textbar{} API:
\href{https://github.com/liferay/liferay-faces-bridge-api/tree/4.x}{4.x}IMPL:
\href{https://github.com/liferay/liferay-faces-bridge-impl/tree/4.x}{4.x}\textbar com.liferay.faces.bridge.api-4.1.0.jarcom.liferay.faces.bridge.impl-4.0.0.jar\textbar2.0\textbar2.2\textbar{}\href{https://www.jcp.org/en/jsr/detail?id=329}{JSR
329}\textbar{}\emph{Includes non-standard bridge extensions for JSF
2.2.}\textbar{} API:
\href{https://github.com/liferay/liferay-faces-bridge-api/tree/3.x}{3.x}IMPL:
\href{https://github.com/liferay/liferay-faces-bridge-impl/tree/3.x}{3.x}\textbar com.liferay.faces.bridge.api-3.1.0.jarcom.liferay.faces.bridge.impl-3.0.0.jar\textbar2.0\textbar2.1\textbar{}\href{https://www.jcp.org/en/jsr/detail?id=329}{JSR
329}\textbar{}\emph{Includes non-standard bridge extensions for JSF
2.1.}\textbar{} API:
\href{https://github.com/liferay/liferay-faces-bridge-api/tree/2.x}{2.x}IMPL:
\href{https://github.com/liferay/liferay-faces-bridge-impl/tree/2.x}{2.x}\textbar com.liferay.faces.bridge.api-2.1.0.jarcom.liferay.faces.bridge.impl-2.0.0.jar\textbar2.0\textbar1.2\textbar{}\href{https://www.jcp.org/en/jsr/detail?id=329}{JSR
329} (MR1)\textbar{}\emph{Includes support for Maintenance Release 1
(MR1).}\textbar{}
1.x\textbar N/A\textbar1.0\textbar1.2\textbar{}\href{https://www.jcp.org/en/jsr/detail?id=301}{JSR
301}\textbar{}\emph{N/A (Not Applicable) since Liferay Faces Bridge has
never implemented JSR 301.}\textbar{}
\noindent\hrulefill
\section{Liferay Faces Bridge Ext}\label{liferay-faces-bridge-ext}
Extension to Liferay Faces Bridge that provides compatibility with
\href{https://liferay.dev/-/portal}{Liferay Portal} and also takes
advantage of Liferay-specific features such as friendly URLs.
\noindent\hrulefill
Branch \textbar Example Artifact \textbar~~Liferay Portal
API~~\textbar~~Bridge API~~\textbar~~Portlet API~~\textbar JSF
API\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/master}{8.x}\textbar com.liferay.faces.bridge.ext-8.0.0.jar\textbar7.3.0+\textbar5.x\textbar3.0\textbar2.3\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/7.x}{7.x}\textbar com.liferay.faces.bridge.ext-7.0.0.jar\textbar7.3.0+\textbar5.x\textbar3.0\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/6.x}{6.x}\textbar com.liferay.faces.bridge.ext-6.0.0.jar\textbar7.3.0+\textbar4.x\textbar2.0\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/5.x}{5.x}\textbar com.liferay.faces.bridge.ext-5.0.4.jar\textbar7.0.x/7.1.x/7.2.x\textbar4.x\textbar2.0\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/4.x}{4.x}\textbar UNUSED\textbar N/A\textbar N/A\textbar N/A\textbar N/A\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/3.x}{3.x}\textbar com.liferay.faces.bridge.ext-3.0.1.jar\textbar6.2.x\textbar4.x\textbar2.0\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/2.x}{2.x}\textbar com.liferay.faces.bridge.ext-2.0.1.jar\textbar6.2.x\textbar3.x\textbar2.0\textbar2.1\textbar{}
\href{https://github.com/liferay/liferay-faces-bridge-ext/tree/1.x}{1.x}\textbar com.liferay.faces.bridge.ext-1.0.1.jar\textbar6.2.x\textbar2.x\textbar2.0\textbar1.2\textbar{}
\noindent\hrulefill
\section{Liferay Faces Portal}\label{liferay-faces-portal}
Provides a suite of JSF components that are based on the JSP tags
provided by \href{https://liferay.dev/-/portal}{Liferay Portal}.
\noindent\hrulefill
Branch\textbar Example Artifact\textbar Liferay Portal
API~~\textbar~~Portlet API\textbar~~JSF API\textbar{}
\href{https://github.com/liferay/liferay-faces-portal/tree/master}{6.x}\textbar com.liferay.faces.portal-6.0.0.jar\textbar7.2+\textbar3.0\textbar2.3\textbar{}
\href{https://github.com/liferay/liferay-faces-portal/tree/5.x}{5.x}\textbar com.liferay.faces.portal-5.0.0.jar\textbar7.2+\textbar3.0\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-portal/tree/4.x}{4.x}\textbar com.liferay.faces.portal-4.0.0.jar\textbar7.2/7.3\textbar2.0\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-portal/tree/3.x}{3.x}\textbar com.liferay.faces.portal-3.0.1.jar\textbar7.0/7.1/7.2\textbar2.0\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-portal/tree/2.x}{2.x}\textbar com.liferay.faces.portal-2.0.1.jar\textbar6.2\textbar2.0\textbar2.1/2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-portal/tree/1.x}{1.x}\textbar com.liferay.faces.portal-1.0.1.jar\textbar6.2\textbar2.0\textbar1.2\textbar{}
\noindent\hrulefill
\section{Liferay Faces Util}\label{liferay-faces-util}
Library that contains general purpose JSF utilities to support many of
the sub-projects that comprise Liferay Faces.
\noindent\hrulefill
Branch\textbar Example Artifact\textbar~~JSF API\textbar{}
\href{https://github.com/liferay/liferay-faces-util/tree/4.x}{4.x}\textbar com.liferay.faces.util-3.1.0.jar\textbar2.3\textbar{}
\href{https://github.com/liferay/liferay-faces-util/tree/3.x}{3.x}\textbar com.liferay.faces.util-3.1.0.jar\textbar2.2\textbar{}
\href{https://github.com/liferay/liferay-faces-util/tree/2.x}{2.x}\textbar com.liferay.faces.util-2.1.0.jar\textbar2.1\textbar{}
\href{https://github.com/liferay/liferay-faces-util/tree/1.x}{1.x}\textbar com.liferay.faces.util-1.1.0.jar\textbar1.2\textbar{}
\noindent\hrulefill
Now that you know all about the Liferay Faces versioning scheme, you may
be curious as to how these components interact with each other. Refer to
the following figure to view the Liferay Faces dependency diagram.
\begin{figure}
\centering
\includegraphics{./images/liferay-faces-dependency-diagram.png}
\caption{The Liferay Faces dependency diagram helps visualize how
components interact and depend on each other.}
\end{figure}
Next, you can view some example configurations to see the new versioning
scheme in action.
\chapter{Understanding Liferay Faces
Bridge}\label{understanding-liferay-faces-bridge}
The Liferay Faces Bridge enables you to deploy JSF web apps as portlets
without writing portlet-specific code. It also contains innovative
features that make it possible to leverage the power of JSF 2.x inside a
portlet application.
Liferay Faces Bridge is distributed in a \texttt{.jar} file. You can add
Liferay Faces Bridge as a dependency to your portlet projects, in order
to deploy your JSF web applications as portlets within JSR 286 (Portlet
2.0) compliant portlet containers, like Liferay Portal 5.2, 6.0, 6.1,
6.2, and 7.0.
The Liferay Faces Bridge project home page can be found
\href{https://community.liferay.com/-/faces}{here}.
To fully understand Liferay Faces Bridge, you must first understand the
portlet bridge standard. Because the Portlet 1.0 and JSF 1.0 specs were
being created at essentially the same time, the Expert Group (EG) for
the JSF specification constructed the JSF framework to be compliant with
portlets. For example, the
\href{https://javaee.github.io/javaee-spec/javadocs/javax/faces/context/ExternalContext.html\#getRequest--}{ExternalContext.getRequest()}
method returns an \texttt{Object} instead of an
\href{https://javaee.github.io/javaee-spec/javadocs/javax/servlet/http/HttpServletRequest.html}{javax.servlet.http.HttpServletRequest}.
When this method is used in a portal, the \texttt{Object} can be cast to
a
\href{http://portals.apache.org/pluto/portlet-2.0-apidocs/javax/portlet/PortletRequest.html}{javax.portlet.PortletRequest}.
Despite the EG's consciousness of portlet compatibility within the
design of JSF, the gap between the portlet and JSF lifecycles had to be
bridged.
Portlet bridge standards and implementations evolved over time.
Starting in 2004, several different JSF portlet bridge implementations
were developed in order to provide JSF developers with the ability to
deploy their JSF web apps as portlets. In 2006, the JCP formed the
Portlet Bridge 1.0 (\href{http://www.jcp.org/en/jsr/detail?id=301}{JSR
301}) EG in order to define a standard bridge API, as well as detailed
requirements for bridge implementations. JSR 301 was released in 2010,
targeting Portlet 1.0 and JSF 1.2.
When the Portlet 2.0 (\href{http://www.jcp.org/en/jsr/detail?id=286}{JSR
286}) standard was released in 2008, it became necessary for the JCP to
form the Portlet Bridge 2.0
(\href{http://www.jcp.org/en/jsr/detail?id=329}{JSR 329}) EG. JSR 329
was also released in 2010, targeting Portlet 2.0 and JSF 1.2.
After the \href{http://www.jcp.org/en/jsr/detail?id=314}{JSR 314} EG
released JSF 2.0 in 2009 and JSF 2.1 in 2010, it became evident that a
Portlet Bridge 3.0 standard would be beneficial. In 2015 the JCP formed
\href{http://www.jcp.org/en/jsr/detail?id=378}{JSR 378}) which is
defining a bridge for Portlet 3.0 and JSF 2.2. There are also variants
of \emph{Liferay Faces Bridge} that support Portlet 2.0 and JSF
1.2/2.1/2.2.
Liferay Faces Bridge is the Reference Implementation (RI) of the Portlet
Bridge Standard. It also contains innovative features that make it
possible to leverage the power of JSF 2.x inside a portlet application.
Now that you're familiar with some of the history of the Portlet Bridge
standards, you'll learn about the responsibilities required of the
portlet bridge.
A JSF portlet bridge aligns the correct phases of the JSF lifecycle with
each phase of the portlet lifecycle. For instance, if a browser sends an
HTTP GET request to a portal page with a JSF portlet in it, the
\texttt{RENDER\_PHASE} is performed in the portlet's lifecycle. The JSF
portlet bridge then initiates the \texttt{RESTORE\_VIEW} and
\texttt{RENDER\_RESPONSE} phases in the JSF lifecycle. Likewise, when an
HTTP POST is executed on a portlet and the portlet enters the
\texttt{ACTION\_PHASE}, then the full JSF lifecycle is initiated by the
bridge.
\begin{figure}
\centering
\includegraphics{./images/lifecycle-bridge.png}
\caption{The different phases of the JSF Lifecycle are executed
depending on which phase of the Portlet lifecycle is being executed.}
\end{figure}
Besides ensuring that the two lifecycles connect correctly, the JSF
portlet bridge also acts as a mediator between the portal URL generator
and JSF navigation rules. JSF portlet bridges ensure that URLs created
by the portal comply with JSF navigation rules, so that a JSF portlet is
able to switch to different views.
The JSR 329/378 standards defines several configuration options prefixed
with the \texttt{javax.portlet.faces} namespace. Liferay Faces Bridge
defines additional implementation-specific options prefixed with the
\texttt{com.liferay.faces.bridge} namespace.
Liferay Faces Bridge is an essential part of the JSF development process
for Liferay DXP. Visit the
\href{/docs/7-1/tutorials/-/knowledge_base/t/jsf-portlets-with-liferay-faces}{JSF
Portlets with Liferay Faces} section of tutorials for more information
on JSF development for Liferay DXP.
\section{Related Topics}\label{related-topics-45}
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-alloy}{Understanding
Liferay Faces Alloy}
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-portal}{Understanding
Liferay Faces Portal}
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder}
\chapter{Understanding Liferay Faces
Alloy}\label{understanding-liferay-faces-alloy}
Liferay Faces Alloy is distributed in a \texttt{.jar} file. You can add
Liferay Faces Alloy as a dependency to your portlet projects, to use
AlloyUI in a way that is consistent with JSF development.
\noindent\hrulefill
\textbf{Note:} AlloyUI is deprecated in Liferay DXP 7.2.
\noindent\hrulefill
During the creation of a JSF portlet in Liferay IDE/Developer Studio,
you have the option of choosing the portlet's JSF Component Suite. The
options include \emph{JSF standard},
\href{http://www.icesoft.org/java/projects/ICEfaces/overview.jsf}{\emph{ICEfaces}},
\href{http://primefaces.org/}{\emph{PrimeFaces}},
\href{http://richfaces.jboss.org/}{\emph{RichFaces}}, and \emph{Liferay
Faces Alloy}.
If you selected the Liferay Faces Alloy JSF Component Suite during your
portlet's setup, the \texttt{.jar} file is included in your portlet
project.
The Liferay Faces Alloy project provides a set of UI components that
utilize AlloyUI. For example, a brief list of some of the supported
\texttt{aui:} tags are listed below:
\begin{itemize}
\tightlist
\item
Input: \texttt{alloy:inputText}, \texttt{alloy:inputDate},
\texttt{alloy:inputFile}
\item
Panel: \texttt{alloy:accordion}, \texttt{alloy:column},
\texttt{alloy:fieldset}, \texttt{alloy:row}
\item
Select: \texttt{alloy:selectOneMenu}, \texttt{alloy:selectOneRadio},
\texttt{alloy:selectStarRating}
\end{itemize}
If you want to utilize Liferay's AlloyUI technology based on YUI3, you
must include the Liferay Faces Alloy \texttt{.jar} file in your JSF
portlet project. If you selected \emph{Liferay Faces Alloy} during your
JSF portlet's setup, you have Liferay Faces Alloy preconfigured in your
project, so you're automatically able to use the \texttt{alloy:} tags.
As you can see, it's extremely easy to configure your JSF application to
use Liferay's AlloyUI tags.
\section{Related Topics}\label{related-topics-46}
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-jsf-portlet-application}{Developing
a JSF Portlet Application}
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-bridge}{Understanding
Liferay Faces Bridge}
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-portal}{Understanding
Liferay Faces Portal}
\chapter{Understanding Liferay Faces
Portal}\label{understanding-liferay-faces-portal}
\emph{Liferay Faces Portal} is distributed in a \texttt{.jar} file. You
can add Liferay Faces Portal as a dependency for your portlet projects
to use its Liferay-specific utilities and UI components. When Liferay
Faces Portal is included in a JSF portlet project, the
\texttt{com.liferay.faces.portal.{[}version{]}.jar} file resides in the
portlet's library.
\begin{figure}
\centering
\includegraphics{./images/jsf-jars-package-explorer.png}
\caption{The required \texttt{.jar} files are downloaded for your JSF
portlet based on the JSF UI Component Suite you configured.}
\end{figure}
Some of the features included in Liferay Faces Portal are:
\begin{itemize}
\item
Utilities: Provides the \texttt{LiferayPortletHelperUtil} which
contains a variety Portlet-API and Liferay-specific convenience
methods.
\item
JSF Components: Provides a set of JSF equivalents for popular Liferay
DXP JSP tags (not exhaustive):
\begin{itemize}
\tightlist
\item
\texttt{liferay-ui:captcha} → \texttt{portal:captcha}
\item
\texttt{liferay-ui:input-editor} → \texttt{portal:inputRichText}
\item
\texttt{liferay-ui:search} → \texttt{portal:inputSearch}
\item
\texttt{liferay-ui:header} → \texttt{portal:header}
\item
\texttt{aui:nav} → \texttt{portal:nav}
\item
\texttt{aui:nav-item} → \texttt{portal:navItem}
\item
\texttt{aui:nav-bar} → \texttt{portal:navBar}
\item
\texttt{liferay-security:permissionsURL} →
\texttt{portal:permissionsURL}
\item
\texttt{liferay-portlet:runtime} → \texttt{portal:runtime}
\end{itemize}
For more information, visit
\url{https://liferayfaces.org/web/guest/portal-showcase}.
\item
Expression Language: Adds a set of EL keywords such as
\texttt{liferay} for getting Liferay-specific info, and \texttt{i18n}
for integration with out-of-the-box Liferay internationalized
messages.
\end{itemize}
Great! You now have an understanding of what Liferay Faces Portal is,
and what it accomplishes in your JSF application.
\section{Related Topics}\label{related-topics-47}
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-jsf-portlet-application}{Developing
a JSF Portlet Application}
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-bridge}{Understanding
Liferay Faces Bridge}
\href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-alloy}{Understanding
Liferay Faces Alloy}
\chapter{Maven Plugins}\label{maven-plugins}
Liferay provides plugins that you can apply to your Maven project. This
reference documentation describes
\begin{itemize}
\tightlist
\item
Configuring the plugin in your \texttt{pom.xml} file.
\item
The plugin's available goals you can leverage.
\item
The plugin's configuration properties.
\end{itemize}
If you're looking for additional instructions on using Maven with your
modules, see the
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Maven} articles.
\chapter{Bundle Support Plugin}\label{bundle-support-plugin}
The Bundle Support plugin lets you use
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} as a Maven project.
\section{Usage}\label{usage-30}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.portal.tools.bundle.support
3.2.5
clean
clean
clean
deploy
deploy
pre-integration-test
...
\end{verbatim}
\section{Goals}\label{goals}
The plugin adds five Maven goals to your project:
Name \textbar{} Description
\hyperref[clean-goals-available-parameters]{bundle-support:clean}
\textbar{} Deletes a file from the \texttt{deploy} directory of a
Liferay bundle.
\hyperref[create-token-goals-available-parameters]{bundle-support:create-token}
\textbar{} Creates a token used to validate your user credentials when
downloading a DXP bundle.
\hyperref[deploy-goals-available-parameters]{bundle-support:deploy}
\textbar{} Deploys the Maven project to the specified Liferay DXP
bundle. \hyperref[dist-goals-available-parameters]{bundle-support:dist}
\textbar{} Creates a distributable Liferay DXP bundle archive file
(e.g., ZIP).
\hyperref[init-goals-available-parameters]{bundle-support:init}
\textbar{} Downloads and installs the specified Liferay DXP version.
\section{clean Goal's Available
Parameters}\label{clean-goals-available-parameters}
You can set the following parameters in the \texttt{clean} execution's
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{liferayHome} \textbar{} \texttt{String} \textbar{}
\texttt{bundles} \textbar{} The directory where your Liferay DXP
instance resides. This can be specified from the command line as
\texttt{-DliferayHome=}. \texttt{fileName} \textbar{} \texttt{String}
\textbar{} \texttt{\$\{project.artifactId\}.\$\{project.packaging\}}
\textbar{} The name of the file to delete from your bundle.
\section{create-token Goal's Available
Parameters}\label{create-token-goals-available-parameters}
You can change the default parameter values of the \texttt{create-token}
goal by creating an \texttt{\textless{}execution\textgreater{}} section
containing \texttt{\textless{}configuration\textgreater{}} tags. For
example,
\begin{verbatim}
create-token
create-token
\end{verbatim}
You can set the following parameters in the \texttt{create-token}
execution's \texttt{\textless{}configuration\textgreater{}} section of
the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{emailAddress} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The email address to use when downloading a DXP
bundle. This email address must match the one registered for your DXP
subscription. \texttt{force} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to override the existing token with a
newly generated one. \texttt{password} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The password to use when downloading
a DXP bundle. This password must match the one registered for your DXP
subscription. \texttt{passwordFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The file to hold your password used when
downloading a DXP bundle. \texttt{tokenFile} \textbar{} \texttt{File}
\textbar{} \texttt{\$\{user.home\}/.liferay/token} \textbar{} The file
to hold the Liferay bundle authentication token. \texttt{tokenUrl}
\textbar{} \texttt{URL} \textbar{}
\texttt{https://releases-cdn.liferay.com/portal/7.1.0-b3/liferay-ce-portal-tomcat-7.1-b3-20180611140920623.zip}
\textbar{} The URL pointing to the bundle Zip to download.
After executing the \texttt{create-token} goal, you're prompted for your
email address and password, both of which are used to generate your
token. It's recommended to configure your email and password from the
command line rather than specifying them in your POM file.
\section{deploy Goal's Available
Parameters}\label{deploy-goals-available-parameters}
You can set the following parameters in the \texttt{deploy} execution's
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{liferayHome} \textbar{} \texttt{String} \textbar{}
\texttt{bundles} \textbar{} The directory where your Liferay DXP
instance resides. This can be specified from the command line as
\texttt{-DliferayHome=}. \texttt{deployFile} \textbar{} \texttt{File}
\textbar{}
\texttt{\$\{project.build.directory\}/\$\{project.build.finalName\}.\$\{project.packaging\}}
\textbar{} The packaged file (e.g., JAR) to deploy to the Liferay
bundle. \texttt{outputFileName} \textbar{} \texttt{String} \textbar{}
\texttt{\$\{project.artifactId\}.\$\{project.packaging\}} \textbar{} The
name of the output file.
\section{dist Goal's Available
Parameters}\label{dist-goals-available-parameters}
You can change the default parameter values of the \texttt{dist} goal by
creating an \texttt{\textless{}execution\textgreater{}} section
containing \texttt{\textless{}configuration\textgreater{}} tags. For
example,
\begin{verbatim}
dist
dist
\end{verbatim}
You can set the following parameters in the \texttt{dist} execution's
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{liferayHome} \textbar{} \texttt{String} \textbar{}
\texttt{bundles} \textbar{} The directory where your Liferay DXP
instance resides. This can be specified from the command line as
\texttt{-DliferayHome=}. \texttt{archiveFileName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The name for the
generated archive file. \texttt{cacheDir} \textbar{} \texttt{File}
\textbar{} \texttt{\$\{user.home\}/.liferay/bundles} \textbar{} The
directory where the downloaded bundle Zip files are stored.
\texttt{configs} \textbar{} \texttt{String} \textbar{} \texttt{configs}
\textbar{} The directory that contains the configuration files.
\texttt{deployFile} \textbar{} \texttt{File}
\textbar{}\texttt{\$\{project.build.directory\}/\$\{project.build.finalName\}.\$\{project.packaging\}}
\textbar{} The packaged file (e.g., JAR) to deploy to the Liferay
bundle. \texttt{environment} \textbar{} \texttt{String} \textbar{}
\texttt{\$\{liferay.workspace.environment\}} \textbar{} The environment
of your Liferay home deployment. (e.g., \texttt{common}, \texttt{dev},
\texttt{local}, \texttt{prod}, and \texttt{uat}). \texttt{format}
\textbar{} \texttt{String} \textbar{} \texttt{zip} \textbar{} The format
type to use when packaging the Liferay bundle as an archive.
\texttt{includeFolder} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to add a parent folder to the archive.
\texttt{outputFileName} \textbar{} \texttt{String} \textbar{}
\texttt{\$\{project.artifactId\}.\$\{project.packaging\}} \textbar{} The
path to the archive file. \texttt{password} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The password if your Liferay
bundle's URL requires authentication. \texttt{stripComponents}
\textbar{} \texttt{int} \textbar{} \texttt{1} \textbar{} The number of
directories to strip when expanding your bundle. \texttt{token}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to use a token to download a Liferay DXP bundle. This should be set to
\texttt{true} when downloading a DXP bundle. \texttt{tokenFile}
\textbar{} \texttt{File} \textbar{}
\texttt{\$\{user.home\}/.liferay/token} \textbar{} The file to hold the
Liferay bundle authentication token. \texttt{url} \textbar{}
\texttt{URL} \textbar{} \texttt{\$\{liferay.workspace.bundle.url\}}
\textbar{} The URL of the Liferay bundle to expand. \texttt{userName}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The user
name if your Liferay bundle's URL requires authentication.
\section{init Goal's Available
Parameters}\label{init-goals-available-parameters}
You can change the default parameter values of the \texttt{init} goal by
creating an \texttt{\textless{}execution\textgreater{}} section
containing \texttt{\textless{}configuration\textgreater{}} tags. For
example,
\begin{verbatim}
init
init
\end{verbatim}
You can set the following parameters in the \texttt{init} execution's
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{liferayHome} \textbar{} \texttt{String} \textbar{}
\texttt{bundles} \textbar{} The directory where your Liferay DXP
instance resides. This can be specified from the command line as
\texttt{-DliferayHome=}. \texttt{cacheDir} \textbar{} \texttt{File}
\textbar{} \texttt{\$\{user.home\}/.liferay/bundles} \textbar{} The
directory where the downloaded bundle Zip files are stored.
\texttt{configs} \textbar{} \texttt{String} \textbar{} \texttt{configs}
\textbar{} The directory that contains the configuration files.
\texttt{environment} \textbar{} \texttt{String} \textbar{}
\texttt{\$\{liferay.workspace.environment\}} \textbar{} The environment
with the settings appropriate for current development (e.g.,
\texttt{common}, \texttt{dev}, \texttt{local}, \texttt{prod}, and
\texttt{uat}). \texttt{password} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The password if your Liferay bundle's URL
requires authentication. \texttt{stripComponents} \textbar{}
\texttt{int} \textbar{} \texttt{1} \textbar{} The number of directories
to strip when expanding your bundle. \texttt{token} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether to use a
token to download a Liferay DXP bundle. This should be set to
\texttt{true} when downloading a DXP bundle. \texttt{tokenFile}
\textbar{} \texttt{File} \textbar{}
\texttt{\$\{user.home\}/.liferay/token} \textbar{} The file to hold the
Liferay bundle authentication token. \texttt{url} \textbar{}
\texttt{URL} \textbar{} \texttt{\$\{liferay.workspace.bundle.url\}}
\textbar{} The URL of the Liferay bundle to expand. \texttt{userName}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The user
name if your Liferay bundle's URL requires authentication.
\chapter{CSS Builder Plugin}\label{css-builder-plugin}
The CSS Builder plugin lets you compile
\href{http://sass-lang.com/}{Sass} files in your project.
\section{Usage}\label{usage-31}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.css.builder
3.0.0
default-build
compile
build
...
\end{verbatim}
You can view an example POM containing the CSS Builder configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/css-builder/samples/pom.xml}{here}.
\section{Goals}\label{goals-1}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{css-builder:build} \textbar{}
Compiles the Sass files in the project.
\section{Available Parameters}\label{available-parameters}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{appendCssImportTimestamps} \textbar{}
\texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether to append
the current timestamp to the URLs in the \texttt{@import} CSS at-rules.
\texttt{baseDir} \textbar{} \texttt{File} \textbar{}
\texttt{"src/META-INF/resources"} \textbar{} The base directory that
contains the SCSS files to compile. \texttt{dirNames} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{}
\texttt{{[}"/"{]}} \textbar{} The name of the directories, relative to
\hyperref[basedir]{\texttt{baseDir}}, which contain the SCSS files to
compile. \texttt{generateSourceMap} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to generate
\href{https://developers.google.com/web/tools/chrome-devtools/debug/readability/source-maps}{source
maps} for easier debugging. \texttt{importDir} \textbar{} \texttt{File}
\textbar{} \texttt{null} \textbar{} The \texttt{META-INF/resources}
directory of the
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-css/frontend-css-common}{Liferay
Frontend Common CSS} artifact. This is required in order to make
\href{http://bourbon.io}{Bourbon} and other CSS libraries available to
the compilation. \texttt{outputDirName} \textbar{} \texttt{String}
\textbar{} \texttt{".sass-cache/"} \textbar{} The name of the
sub-directories where the SCSS files are compiled to. For each directory
that contains SCSS files, a sub-directory with this name is created.
\texttt{precision} \textbar{} \texttt{int} \textbar{} \texttt{9}
\textbar{} The numeric precision of numbers in Sass.
\texttt{rtlExcludedPathRegexps} \textbar{}
\texttt{List\textless{}String\textgreater{}} \textbar{} \textbar{} The
SCSS file patterns to exclude when converting for right-to-left (RTL)
support. \texttt{sassCompilerClassName} \textbar{} \texttt{String}
\textbar{} \texttt{"jni"} \textbar{} The type of Sass compiler to use.
Supported values are \texttt{"jni"} and \texttt{"ruby"}. The Ruby Sass
compiler requires \texttt{com.liferay.sass.compiler.ruby.jar},
\texttt{com.liferay.ruby.gems.jar}, and \texttt{jruby-complete.jar} to
be added to the classpath.
You can also manage the \texttt{com.liferay.frontend.css.common} default
theme dependency provided by the CSS Builder in your \texttt{pom.xml}.
This can be modified by adding it as a project dependency:
\begin{verbatim}
...
com.liferay
com.liferay.frontend.css.common
3.0.1
provided
...
\end{verbatim}
There are additional Liferay theme-related dependencies you can manage
this way that are provided by the Theme Builder. See
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-plugin}{this
section} for more information.
\chapter{DB Support Plugin}\label{db-support-plugin}
The DB Support plugin lets you run the Liferay DB Support tool to
execute certain actions on a local Liferay DXP database. The following
actions are available:
\begin{itemize}
\tightlist
\item
Cleans the Liferay database from the Service Builder tables and rows
of a module.
\end{itemize}
\section{Usage}\label{usage-32}
To use the plugin, include it in your project's \texttt{pom.xml} file:
\begin{verbatim}
...
com.liferay
com.liferay.portal.tools.db.support
1.0.6
org.hsqldb
hsqldb
2.4.0
...
\end{verbatim}
Also notice the configured plugin dependency. You must configure the
JDBC driver used by your Liferay DXP bundle so the DB Support plugin can
properly manage your database. Replace the HSQLDB driver listed above
with your custom database's JDBC driver.
\section{Goals}\label{goals-2}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{db-support:clean-service-builder}
\textbar{} Cleans the Liferay DXP database from the Service Builder
tables and rows of a module.
\section{Available Parameters}\label{available-parameters-1}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{password} \textbar{} \texttt{String} \textbar{}
\texttt{jdbc.default.password} \textbar{} The user password for
connecting to the Liferay DXP database. \texttt{propertiesFile}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
\texttt{portal-ext.properties} file which contains the JDBC settings for
connecting to the Liferay DXP database. \texttt{serviceXmlFile}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
\texttt{service.xml} file of the module. \texttt{servletContextName}
\textbar{} \texttt{String} \textbar{} \texttt{null} \textbar{} The
servlet context name (usually the value of the
\texttt{Bundle-Symbolic-Name} manifest header) of the module.
\texttt{url} \textbar{} \texttt{String} \textbar{}
\texttt{jdbc.default.url} \textbar{} The JDBC URL for connecting to the
Liferay DXP database. \texttt{userName} \textbar{} \texttt{String}
\textbar{} \texttt{jdbc.default.username} \textbar{} The user name for
connecting to the Liferay DXP database.
\chapter{Deployment Helper Plugin}\label{deployment-helper-plugin}
The Deployment Helper plugin lets you create a cluster deployable WAR
from your OSGi artifacts.
\section{Usage}\label{usage-33}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.deployment.helper
1.0.4
...
\end{verbatim}
You can view an example POM containing the Deployment Helper
configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/deployment-helper/samples/pom.xml}{here}.
\section{Goals}\label{goals-3}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{deployment-helper:build} \textbar{}
Builds a WAR which contains one or more files that are copied once the
WAR is deployed.
\section{Available Parameters}\label{available-parameters-2}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{deploymentFileNames} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The files or directories to include
in the WAR and copy once the WAR is deployed. If a directory is added to
this collection, all the JAR files contained in the directory are
included in the WAR. \texttt{deploymentPath} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The directory to which the included
files are copied. \texttt{outputFileName} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The WAR file to build.
\chapter{Javadoc Formatter Plugin}\label{javadoc-formatter-plugin}
The Javadoc Formatter plugin lets you format project Javadoc comments.
The tool lets you generate:
\begin{itemize}
\tightlist
\item
Default
\href{http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html\#@author}{\texttt{@author}}
tags to all classes.
\item
Comment stubs to classes, fields, and methods.
\item
Missing
\href{https://docs.oracle.com/javase/8/docs/api/java/lang/Override.html}{\texttt{@Override}}
annotations.
\item
An XML representation of the Javadoc comments, which can be used by
tools in order to index the Javadocs of the project.
\end{itemize}
\section{Usage}\label{usage-34}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.javadoc.formatter
1.0.32
...
\end{verbatim}
You can view an example POM containing the Javadoc Formatter
configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/javadoc-formatter/samples/pom.xml}{here}.
\section{Goals}\label{goals-4}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{javadoc-formatter:format} \textbar{}
Runs the Liferay Javadoc Formatter to format files.
\section{Available Parameters}\label{available-parameters-3}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{author} \textbar{} \texttt{String} \textbar{}
\texttt{"Brian\ Wing\ Shun\ Chan"} \textbar{} The value of the
\texttt{@author} tag to add at class level if missing.
\texttt{generateXml} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to generate a XML representation of
the Javadoc comments. The XML files are generated in the
\texttt{src/main/resources} directory only if the Java files are
contained in \texttt{src/main/java}. \texttt{initializeMissingJavadocs}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to add comment stubs at the class, field, and method levels. If
\texttt{false}, only the class-level \texttt{@author} is added.
\texttt{inputDirName} \textbar{} \texttt{String} \textbar{}
\texttt{"./"} \textbar{} The root directory to begin searching for Java
files to format. \texttt{limits} \textbar{} \texttt{String{[}{]}}
\textbar{} \texttt{{[}{]}} \textbar{} The Java file name patterns,
relative to the working directory, to include when formatting Javadoc
comments. The patterns must be specified without the \texttt{.java} file
type suffix. If empty, all Java files are formatted.
\texttt{outputFilePrefix} \textbar{} \texttt{String} \textbar{}
\texttt{"javadocs"} \textbar{} The file name prefix of the XML
representation of the Javadoc comments. If \texttt{generateXML} is
\texttt{false}, this property is not used. \texttt{updateJavadocs}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to fix existing comment blocks by adding missing tags.
\chapter{Lang Builder Plugin}\label{lang-builder-plugin}
The Lang Builder plugin lets you sort and translate the language keys in
your project.
\section{Usage}\label{usage-35}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.lang.builder
1.0.31
...
\end{verbatim}
You can view an example POM containing the Lang Builder configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/lang-builder/samples/pom.xml}{here}.
\section{Goals}\label{goals-5}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{lang-builder:build} \textbar{} Runs
Liferay Lang Builder to translate language property files.
\section{Available Parameters}\label{available-parameters-4}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{excludedLanguageIds} \textbar{}
\texttt{String{[}{]}} \textbar{}
\texttt{\{"da",\ "de",\ "fi",\ "ja",\ "nl",\ "pt\_PT",\ "sv"\}}
\textbar{} The language IDs to exclude in the automatic translation.
\texttt{langDirName} \textbar{} \texttt{String} \textbar{}
\texttt{"src/content"} \textbar{} The directory where the language
properties files are saved. \texttt{langFileName} \textbar{}
\texttt{String} \textbar{} \texttt{"Language"} \textbar{} The file name
prefix of the language properties files (e.g.,
\texttt{Language\_it.properties}). \texttt{plugin} \textbar{}
\texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether to check
for duplicate language keys between the project and the portal.
\texttt{portalLanguagePropertiesFileName} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} The \texttt{Language.properties}
file of the portal. \texttt{translate} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} \textbar{} Whether to translate the language
keys and generate a language properties file for each locale that's
supported by Liferay DXP. \texttt{translateSubscriptionKey} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The subscription key
for Microsoft Translation integration. Subscription to the Translator
Text Translation API on Microsoft Cognitive Services is required. Basic
subscriptions, up to 2 million characters a month, are free.
\chapter{REST Builder Plugin}\label{rest-builder-plugin}
The REST Builder plugin lets you generate a REST layer defined in the
REST Builder \texttt{rest-config.yaml} and \texttt{rest-openapi.yaml}
files.
\section{Usage}\label{usage-36}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.portal.tools.rest.builder
1.0.22
...
\end{verbatim}
You can view an example POM containing the REST Builder configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/portal-tools-rest-builder/samples/pom.xml}{here}.
\section{Goals}\label{goals-6}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{rest-builder:build} \textbar{} Runs
the Liferay REST Builder.
\section{Available Parameters}\label{available-parameters-5}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{copyrightFile} \textbar{} \texttt{File} \textbar{}
\texttt{null} \textbar{} The file that contains the copyright header.
\texttt{restConfigDir} \textbar{} \texttt{File} \textbar{}
\texttt{\$\{project.projectDir\}} \textbar{} The directory that contains
the \texttt{rest-config.yaml} and \texttt{rest-openapi.yaml} files.
\chapter{Service Builder Plugin}\label{service-builder-plugin}
The Service Builder plugin lets you generate a service layer defined in
a \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} \texttt{service.xml} file. Visit the
\href{/docs/7-2/reference/-/knowledge_base/r/using-service-builder-in-a-maven-project}{Using
Service Builder in a Maven Project} tutorial to learn more about
applying Service Builder to your Maven project.
\section{Usage}\label{usage-37}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.portal.tools.service.builder
1.0.292
...
\end{verbatim}
You can view an example POM containing the Service Builder configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/portal-tools-service-builder/samples/pom.xml}{here}.
\section{Goals}\label{goals-7}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{service-builder:build} \textbar{}
Runs the Liferay Service Builder.
\section{Available Parameters}\label{available-parameters-6}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{apiDirName} \textbar{} \texttt{String} \textbar{}
\texttt{"../portal-kernel/src"} \textbar{} A directory where the service
API Java source files are generated.
\texttt{autoImportDefaultReferences} \textbar{} \texttt{boolean}
\textbar{} \texttt{true} \textbar{} Whether to automatically add default
references, like \texttt{com.liferay.portal.ClassName},
\texttt{com.liferay.portal.Resource} and
\texttt{com.liferay.portal.User}, to the services.
\texttt{autoNamespaceTables} \textbar{} \texttt{boolean} \textbar{}
\texttt{null} \textbar{} Whether to prefix table names by the namespace
specified in the \texttt{service.xml} file. \texttt{beanLocatorUtil}
\textbar{} \texttt{String} \textbar{}
\texttt{"com.liferay.portal.kernel.bean.PortalBeanLocatorUtil"}
\textbar{} The fully qualified class name of a bean locator class to use
in the generated service classes. \texttt{buildNumber} \textbar{}
\texttt{long} \textbar{} \texttt{1} \textbar{} A specific value to
assign the \texttt{build.number} property in the
\texttt{service.properties} file. \texttt{buildNumberIncrement}
\textbar{} \texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether
to automatically increment the \texttt{build.number} property in the
\texttt{service.properties} file by one at every service generation.
\texttt{databaseNameMaxLength} \textbar{} \texttt{int} \textbar{}
\texttt{30} \textbar{} The upper bound for database table and column
name lengths to ensure it works on all databases. \texttt{hbmFileName}
\textbar{} \texttt{String} \textbar{}
\texttt{"src/META-INF/portal-hbm.xml"} \textbar{} A Hibernate Mapping
file to generate. \texttt{implDirName} \textbar{} \texttt{String}
\textbar{} \texttt{"src"} \textbar{} A directory where the service Java
source files are generated. \texttt{inputFileName} \textbar{}
\texttt{String} \textbar{} \texttt{"service.xml"} \textbar{} The
project's \texttt{service.xml} file. \texttt{modelHintsConfigs}
\textbar{} \texttt{String} \textbar{}
\texttt{"classpath*:META-INF/portal-model-hints.xml,\ META-INF/portal-model-hints.xml,\ classpath*:META-INF/ext-model-hints.xml,\ classpath*:META-INF/portlet-model-hints.xml"}
\textbar{} Paths to the model hints files for Liferay Service Builder to
use in generating the service layer. \texttt{modelHintsFileName}
\textbar{} \texttt{String} \textbar{}
\texttt{"src/META-INF/portal-model-hints.xml"} \textbar{} A model hints
file for the project. \texttt{osgiModule} \textbar{} \texttt{boolean}
\textbar{} \texttt{null} \textbar{} Whether to generate the service
layer for OSGi modules. \texttt{pluginName} \textbar{} \texttt{String}
\textbar{} \texttt{null} \textbar{} If specified, a plugin can enable
additional generation features, such as \texttt{Clp} class generation,
for non-OSGi modules. \texttt{propsUtil} \textbar{} \texttt{String}
\textbar{} \texttt{"com.liferay.portal.util.PropsUtil"} \textbar{} The
fully qualified class name of the service properties util class to
generate. \texttt{readOnlyPrefixes} \textbar{} \texttt{String}
\textbar{} \texttt{"fetch,\ get,\ has,\ is,\ load,\ reindex,\ search"}
\textbar{} Prefixes of methods to consider read-only.
\texttt{resourceActionsConfigs} \textbar{} \texttt{String} \textbar{}
\texttt{"META-INF/resource-actions/default.xml,\ resource-actions/default.xml"}
\textbar{} Paths to the
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{resource
actions} files for Liferay Service Builder to use in generating the
service layer. \texttt{resourcesDirName} \textbar{} \texttt{String}
\textbar{} \texttt{"src"} \textbar{} A directory where the service
non-Java files are generated. \texttt{springFileName} \textbar{}
\texttt{String} \textbar{} \texttt{"src/META-INF/portal-spring.xml"}
\textbar{} A service Spring file to generate. \texttt{springNamespaces}
\textbar{} \texttt{String} \textbar{} \texttt{"beans"} \textbar{}
Namespaces of Spring XML Schemas to add to the service Spring file.
\texttt{sqlDirName} \textbar{} \texttt{String} \textbar{}
\texttt{"../sql"} \textbar{} A directory where the SQL files are
generated. \texttt{sqlFileName} \textbar{} \texttt{String} \textbar{}
\texttt{"portal-tables.sql"} \textbar{} A name (relative to
\texttt{sqlDir}) for the file in which the SQL table creation
instructions are generated. \texttt{sqlIndexesFileName} \textbar{}
\texttt{String} \textbar{} \texttt{"indexes.sql"} \textbar{} A name
(relative to \texttt{sqlDir}) for the file in which the SQL index
creation instructions are generated. \texttt{sqlSequencesFileName}
\textbar{} \texttt{String} \textbar{} \texttt{"sequences.sql"}
\textbar{} A name (relative to \texttt{sqlDir}) for the file in which
the SQL sequence creation instructions are generated.
\texttt{targetEntityName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} If specified, it's the name of the entity for
which Liferay Service Builder should generate the service.
\texttt{testDirName} \textbar{} \texttt{String} \textbar{}
\texttt{"test/integration"} \textbar{} If specified, it's a directory
where integration test Java source files are generated.
\chapter{Source Formatter Plugin}\label{source-formatter-plugin}
The Source Formatter plugin formats project files according to Liferay's
source formatting standards. For more documentation on Source Formatter
specific functionality, visit the tool's
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/source-formatter/documentation}{documentation}
folder.
\section{Usage}\label{usage-38}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.source.formatter
1.0.885
process-sources
format
...
\end{verbatim}
You can view an example POM containing the Source Formatter
configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/source-formatter/samples/pom.xml}{here}.
\section{Goals}\label{goals-8}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{source-formatter:format} \textbar{}
Runs the Liferay Source Formatter to format source formatting errors.
\section{Available Parameters}\label{available-parameters-7}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{autoFix} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to automatically fix source formatting
errors. \texttt{baseDir} \textbar{} \texttt{String} \textbar{}
\texttt{"./"} \textbar{} The Source Formatter base directory.
\emph{(Read-only)} \texttt{fileNames} \textbar{} \texttt{String{[}{]}}
\textbar{} \texttt{null} \textbar{} The file names to format, relative
to the project directory. If \texttt{null}, all files contained in
\texttt{baseDir} will be formatted. \texttt{formatCurrentBranch}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to format only the files contained in \texttt{baseDir} that are added or
modified in the current Git branch. \texttt{formatLatestAuthor}
\textbar{} \texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether
to format only the files contained in \texttt{baseDir} that are added or
modified in the latest Git commits of the same author.
\texttt{formatLocalChanges} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to format only the unstaged files
contained in \texttt{baseDir}. \texttt{gitWorkingBranchName} \textbar{}
\texttt{String} \textbar{} \texttt{"master"} \textbar{} The Git working
branch name. \texttt{includeSubrepositories} \textbar{} \texttt{boolean}
\textbar{} \texttt{false} \textbar{} Whether to format files that are in
read-only subrepositories. \texttt{maxLineLength} \textbar{}
\texttt{int} \textbar{} \texttt{80} \textbar{} The maximum number of
characters allowed in Java files. \texttt{printErrors} \textbar{}
\texttt{boolean} \textbar{} \texttt{true} \textbar{} Whether to print
formatting errors on the Standard Output stream.
\texttt{processorThreadCount} \textbar{} \texttt{int} \textbar{}
\texttt{5} \textbar{} The number of threads used by Source Formatter.
\texttt{showDocumentation} \textbar{} \texttt{boolean} \textbar{}
\texttt{false} \textbar{} Whether to show the documentation for the
source formatting issues, if present. \texttt{throwException} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether to fail
the build if formatting errors are found.
\chapter{Theme Builder Plugin}\label{theme-builder-plugin}
The Theme Builder plugin lets you build Liferay theme files in your
project. Visit the
\href{/docs/7-2/reference/-/knowledge_base/r/building-a-theme-with-maven}{Building
a Theme with Maven} tutorial to learn more about applying Theme Builder
to your Maven project.
\section{Usage}\label{usage-39}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.portal.tools.theme.builder
1.1.7
generate-resources
build
...
\end{verbatim}
You can view an example POM containing the Theme Builder configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/portal-tools-theme-builder/samples/pom.xml}{here}.
\section{Goals}\label{goals-9}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{theme-builder:build} \textbar{}
Builds the theme files.
\section{Available Parameters}\label{available-parameters-8}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{diffsDir} \textbar{} \texttt{File} \textbar{}
\texttt{\$\{maven.war.src\}} \textbar{} The directory that contains the
files to copy over the parent theme. \texttt{name} \textbar{}
\texttt{String} \textbar{} \texttt{\$\{project.artifactId\}} \textbar{}
The name of the new theme. \texttt{outputDir} \textbar{} \texttt{File}
\textbar{}
\texttt{\$\{project.build.directory\}/\$\{project.build.finalName\}}
\textbar{} The directory where to build the theme. \texttt{parentDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
directory of the parent theme. \texttt{parentName} \textbar{}
\texttt{String} \textbar{} \texttt{null} \textbar{} The name of the
parent theme. \texttt{templateExtension} \textbar{} \texttt{String}
\textbar{} \texttt{"ftl"} \textbar{} The extension of the template
files, usually \texttt{"ftl"} or \texttt{"vm"}. \texttt{unstyledDir}
\textbar{} \texttt{File} \textbar{} \texttt{null} \textbar{} The
directory of
\href{https://github.com/liferay/liferay-portal/tree/master/modules/apps/frontend-theme/frontend-theme-unstyled}{Liferay
Frontend Theme Unstyled}.
You can also manage the \texttt{com.liferay.frontend.theme.styled} and
\texttt{com.liferay.frontend.theme.unstyled} default theme dependencies
provided by the Theme Builder in your \texttt{pom.xml}. They can be
modified by adding them as project dependencies:
\begin{verbatim}
...
...
com.liferay
com.liferay.frontend.theme.styled
3.0.4
provided
com.liferay
com.liferay.frontend.theme.unstyled
3.0.4
provided
\end{verbatim}
There is an additional Liferay theme-related dependency you can manage
this way that's provided by the CSS Builder. See
\href{/docs/7-2/reference/-/knowledge_base/r/css-builder-plugin}{this
section} for more information.
\chapter{TLD Formatter Plugin}\label{tld-formatter-plugin}
The TLD Formatter plugin lets you format a project's TLD files.
\section{Usage}\label{usage-40}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.tld.formatter
1.0.5
...
\end{verbatim}
You can view an example POM containing the TLD Formatter configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/tld-formatter/samples/pom.xml}{here}.
\section{Goals}\label{goals-10}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{tld-formatter:format} \textbar{}
Runs the Liferay TLD Formatter to format files.
\section{Available Parameters}\label{available-parameters-9}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{baseDirName} \textbar{} \texttt{String} \textbar{}
\texttt{"./"} \textbar{} The base directory to begin searching for TLD
files to format. \texttt{plugin} \textbar{} \texttt{boolean} \textbar{}
\texttt{true} \textbar{} Whether to format all the TLD files contained
in the working directory. If \texttt{false}, all
\texttt{liferay-portlet-ext.tld} files are ignored.
\chapter{WSDD Builder Plugin}\label{wsdd-builder-plugin}
The WSDD Builder plugin lets you generate the
\href{http://axis.apache.org/axis/}{Apache Axis} Web Service Deployment
Descriptor (WSDD) files from a
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} \texttt{service.xml} file.
\section{Usage}\label{usage-41}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.portal.tools.wsdd.builder
1.0.10
...
\end{verbatim}
You can view an example POM containing the WSDD Builder configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/portal-tools-wsdd-builder/samples/pom.xml}{here}.
\section{Goals}\label{goals-11}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{wsdd-builder:build} \textbar{} Runs
the Liferay WSDD Builder to generate the WSDD files.
\section{Available Parameters}\label{available-parameters-10}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{classPath} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The classpath that the Liferay WSDD Builder
uses to generate WSDD files. \texttt{inputFileName} \textbar{}
\texttt{String} \textbar{} \texttt{"service.xml"} \textbar{} The file
from which to generate the WSDD files. \texttt{outputDirName} \textbar{}
\texttt{String} \textbar{} \texttt{"src"} \textbar{} The directory where
the \texttt{*\_deploy.wsdd} and \texttt{*\_undeploy.wsdd} files are
generated. \texttt{serverConfigFileName} \textbar{} \texttt{String}
\textbar{} \texttt{"server-config.wsdd"} \textbar{} The file to
generate. \texttt{serviceNamespace} \textbar{} \texttt{String}
\textbar{} \texttt{"Plugin"} \textbar{} The namespace for the WSDD
Service.
\chapter{XML Formatter Plugin}\label{xml-formatter-plugin}
The XML Formatter plugin lets you format a project's XML files.
\section{Usage}\label{usage-42}
To use the plugin, include it in your project's root \texttt{pom.xml}
file:
\begin{verbatim}
...
com.liferay
com.liferay.xml.formatter
1.0.5
...
\end{verbatim}
You can view an example POM containing the XML Formatter configuration
\href{https://github.com/liferay/liferay-portal/blob/master/modules/util/xml-formatter/samples/pom.xml}{here}.
\section{Goals}\label{goals-12}
The plugin adds one Maven goal to your project:
Name \textbar{} Description \texttt{xml-formatter:format} \textbar{}
Runs the Liferay XML Formatter to format the project files.
\section{Available Parameters}\label{available-parameters-11}
You can set the following parameters in the
\texttt{\textless{}configuration\textgreater{}} section of the POM:
Parameter Name \textbar{} Type \textbar{} Default Value \textbar{}
Description \texttt{fileName} \textbar{} \texttt{String} \textbar{}
\texttt{null} \textbar{} The XML file to format. This plugin only lets
you format one XML file at a time. \texttt{stripComments} \textbar{}
\texttt{boolean} \textbar{} \texttt{false} \textbar{} Whether to remove
all the comments from the XML file.
\chapter{PortletMVC4Spring}\label{portletmvc4spring}
PortletMVC4Spring integrates Spring, the Spring Web Framework, and the
MVC design pattern with portlet development. As such, it uses
configuration files from each of these areas and provides new
annotations that facilitate portlet application development. Here are
the PortletMVC4Spring reference topics:
\begin{itemize}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-annotations}{PortletMVC4Spring
Annotations}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-configuration-files}{PortletMVC4Spring
Configuration Files}
\end{itemize}
\chapter{PortletMVC4Spring Project
Anatomy}\label{portletmvc4spring-project-anatomy}
PortletMVC4Spring portlets are packaged in WARs. Liferay provide Maven
archetypes for creating projects configured to use JSP/JSPX and
Thymeleaf templates. Their commands are listed below. The
PortletMVC4Spring project structure follows the commands.
\section{Maven Commands for Generating PortletMVC4Spring
Projects}\label{maven-commands-for-generating-portletmvc4spring-projects}
Here are Maven commands for generating PortletMVC4Spring portlet
projects that use JSPX and \href{https://www.thymeleaf.org}{Thymeleaf}
View templates:
\section{SP/JSPX Form Portlet}\label{spjspx-form-portlet}
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay.portletmvc4spring.archetype \
-DarchetypeArtifactId=com.liferay.portletmvc4spring.archetype.form.jsp.portlet \
-DarchetypeVersion=5.1.0 \
-DgroupId=com.mycompany \
-DartifactId=com.mycompany.my.form.jsp.portlet
\end{verbatim}
\section{Thymeleaf Form Portlet}\label{thymeleaf-form-portlet}
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay.portletmvc4spring.archetype \
-DarchetypeArtifactId=com.liferay.portletmvc4spring.archetype.form.thymeleaf.portlet \
-DarchetypeVersion=5.1.0 \
-DgroupId=com.mycompany \
-DartifactId=com.mycompany.my.form.thymeleaf.portlet
\end{verbatim}
\section{Project Structure}\label{project-structure}
The Maven commands generate a project that includes Model and Controller
classes, View templates, a resource bundle, a stylesheet, and more. The
\href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-configuration-files}{Spring
contexts and configuration files} set PortletMVC4Spring development
essentials. Here's the resulting project structure:
\begin{itemize}
\tightlist
\item
\texttt{{[}com.mycompany.my.form.jsp.portlet{]}}/ → Arbitrary project
name
\begin{itemize}
\tightlist
\item
\texttt{src/}
\begin{itemize}
\tightlist
\item
\texttt{main/}
\begin{itemize}
\tightlist
\item
\texttt{java/{[}my-package-path{]}/}
\begin{itemize}
\tightlist
\item
\texttt{controller/} → Sub-package for Controller classes
(optional)
\item
\texttt{dto/} → Sub-package for Model (data transfer object)
classes (optional)
\item
\texttt{resources/} → Resources to include in the class path -
\texttt{content/} → Resource bundles -
\texttt{log4j.properties} → Log4J logging configuration
\item
\texttt{webapp/}
\begin{itemize}
\tightlist
\item
\texttt{resources/}
\begin{itemize}
\tightlist
\item
\texttt{css/} → Style sheets
\item
\texttt{images/} → Images
\end{itemize}
\item
\texttt{WEB-INF/}
\begin{itemize}
\tightlist
\item
\texttt{spring-context/} → Contexts
\begin{itemize}
\tightlist
\item
\texttt{portlet/} → Portlet contexts
\begin{itemize}
\tightlist
\item
\texttt{portlet1-context.xml} → Portlet context
\end{itemize}
\item
\texttt{portlet-application-context.xml} → Application
context
\end{itemize}
\item
\texttt{views/} → View templates
\item
\texttt{liferay-display.xml} → Portlet display
configuration
\item
\texttt{liferay-plugin-package.properties} → Packaging
descriptor
\item
\texttt{liferay-portlet.xml} → Liferay-specific portlet
configuration
\item
\texttt{portlet.xml} → Portlet configuration
\item
\texttt{web.xml} → Web application configuration
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{test/java/} → Test source files
\end{itemize}
\item
\texttt{build.gradle} → Gradle build file
\item
\texttt{pom.xml} → Maven POM
\end{itemize}
\end{itemize}
\chapter{PortletMVC4Spring
Annotations}\label{portletmvc4spring-annotations}
PortletMVC4Spring provides several annotations for mapping requests to
controller classes and controller methods.
\section{\texorpdfstring{\texttt{@RenderMapping} Annotation
Examples}{@RenderMapping Annotation Examples}}\label{rendermapping-annotation-examples}
The following table describes some \texttt{@RenderMapping} annotation
examples.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5769}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.4231}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Example
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{@RenderMapping} & Handle primary render requests if no other
handler methods match the render request. \\
\texttt{@RenderMapping(params\ =\ "javax.portlet.action=success")} &
Handle the render request if has parameter a parameter setting
\texttt{javax.portlet.action=success}. \\
\texttt{@RenderMapping(param\ =\ "foo")} & Handle the request if it has
a parameter named \texttt{foo}, regardless of its value. \\
\texttt{@RenderMapping(param\ =\ "!bar")} & Handle the request as long
as it has not parameter named \texttt{bar}. \\
\texttt{@RenderMapping(windowState\ =\ "MAXIMIZED")} & Handle the
request if the window state is \texttt{MAXIMIZED}. Note, supported
portlet window states are \texttt{NORMAL}, \texttt{MAXIMIZED}, and
\texttt{MINIMIZED}. \\
\end{longtable}
\noindent\hrulefill
\section{\texorpdfstring{\texttt{@ActionMapping} Annotation
Examples}{@ActionMapping Annotation Examples}}\label{actionmapping-annotation-examples}
The table below describes some \texttt{@ActionMapping} annotation
examples.
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5769}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.4231}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Example
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{@ActionMapping} & Handle primary action requests if no other
handler methods match the action request. \\
\texttt{@ActionMapping(params\ =\ some.param=yourValue")} & Handle the
action request if has parameter a parameter setting
\texttt{javax.portlet.action=success}. \\
\texttt{@ActionMapping(param\ =\ "foo")} & Handle the request if it has
a parameter named \texttt{foo}, regardless of its value. \\
\texttt{@ActionMapping(param\ =\ "!bar")} & Handle the request as long
as it has not parameter named \texttt{bar}. \\
\end{longtable}
\chapter{PortletMVC4Spring Configuration
Files}\label{portletmvc4spring-configuration-files}
A PortletMVC4Spring application has these descriptors, Spring contexts,
and properties files in its \texttt{WEB-INF} folder:
\begin{itemize}
\tightlist
\item
\texttt{web.xml} → Web application descriptor
\item
\texttt{portlet.xml} → Portlet application descriptor
\item
\texttt{liferay-portlet.xml} → Liferay-specific portlet descriptor
\item
\texttt{liferay-display.xml} → Liferay-specific display descriptor
\item
\texttt{spring-context/portlet-application-context.xml} → Portlet
application context
\item
\texttt{spring-context/portlet/{[}portlet{]}-context.xml} → Portlet
context
\item
\texttt{liferay-plugin-package.properties} → Packaging descriptor
\end{itemize}
Examples of each file are provided and portlet-specific content is
highlighted.
\section{web.xml}\label{web.xml}
The servlet container processes the \texttt{web.xml}. This file
specifies the servlet that render's the portlet and the portlet
application's context, servlet, filters, listeners, and more. Here's an
example \texttt{web.xml}:
\begin{verbatim}
contextConfigLocation
/WEB-INF/spring-context/portlet-application-context.xml
ViewRendererServlet
com.liferay.portletmvc4spring.ViewRendererServlet
1
ViewRendererServlet
/WEB-INF/servlet/view
delegatingFilterProxy
org.springframework.web.filter.DelegatingFilterProxy
delegatingFilterProxy
/WEB-INF/servlet/view
FORWARD
INCLUDE
org.springframework.web.context.ContextLoaderListener
\end{verbatim}
The \texttt{\textless{}context-param/\textgreater{}} element gives the
path to the portlet application context (discussed later):
\begin{verbatim}
contextConfigLocation
/WEB-INF/spring-context/portlet-application-context.xml
\end{verbatim}
The \texttt{\textless{}servlet/\textgreater{}} and
\texttt{\textless{}servlet-mapping/\textgreater{}} elements set the
servlet and the internal location for its views.
\begin{verbatim}
ViewRendererServlet
com.liferay.portletmvc4spring.ViewRendererServlet
1
ViewRendererServlet
/WEB-INF/servlet/view
\end{verbatim}
The
\href{https://liferay.github.io/portletmvc4spring/apidocs/com/liferay/portletmvc4spring/ViewRendererServlet.html}{\texttt{ViewRendererServlet}}.
converts portlet requests into servlet requests and enables the view to
be rendered using the Spring Web MVC infrastructure and the
infrastructure's renderers for JSP, Thymeleaf, Velocity, and more.
The filter and filter mappings are set to forward and include servlet
views as necessary.
\begin{verbatim}
delegatingFilterProxy
org.springframework.web.filter.DelegatingFilterProxy
delegatingFilterProxy
/WEB-INF/servlet/view
FORWARD
INCLUDE
\end{verbatim}
A listener is configured for processing the application's contexts.
\begin{verbatim}
org.springframework.web.context.ContextLoaderListener
\end{verbatim}
Liferay's project archetypes generate all this boilerplate code.
\section{portlet.xml}\label{portlet.xml}
The \texttt{portlet.xml} file describes the portlet application to the
portlet container. Here's an example:
\begin{verbatim}
portlet1
com.mycompany.my.form.jsp.portlet
com.liferay.portletmvc4spring.DispatcherPortlet
contextConfigLocation
/WEB-INF/spring-context/portlet/portlet1-context.xml
0
text/html
view
content.portlet1
com.mycompany.my.form.jsp.portlet
com.mycompany.my.form.jsp.portlet
com.mycompany.my.form.jsp.portlet
administrator
guest
power-user
user
SpringSecurityPortletFilter
com.liferay.portletmvc4spring.security.SpringSecurityPortletFilter
ACTION_PHASE
RENDER_PHASE
RESOURCE_PHASE
SpringSecurityPortletFilter
portlet1
\end{verbatim}
This application has one portlet named \texttt{portlet1}.
\begin{verbatim}
portlet1
com.mycompany.my.form.jsp.portlet
com.liferay.portletmvc4spring.DispatcherPortlet
\end{verbatim}
The \texttt{\textless{}portlet-name/\textgreater{}} is internal and the
\texttt{\textless{}display-name/\textgreater{}} is shown to users.
\texttt{\textless{}portlet-class/\textgreater{}} specifies the portlet's
Java class.
\textbf{Important:} All PortletMVC4Spring portlets must specify
\texttt{\textless{}portlet-class\textgreater{}com.liferay.portletmvc4spring.DispatcherPortlet\textless{}/portlet-class\textgreater{}}.
The \texttt{\textless{}supports/\textgreater{}} element must declare the
mime type that the portlet templates use.
The \texttt{\textless{}resource-bundle/\textgreater{}} sets the path to
the portlet's localized Java message properties. For example, the
element refers to properties at \texttt{content/portlet1.properties}:
\begin{verbatim}
content.portlet1
\end{verbatim}
The \texttt{\textless{}portlet-info/\textgreater{}} element lists the
portlet's titles and reserved keyword.
The \texttt{\textless{}security-role-ref/\textgreater{}} elements
declare default user roles the portlet accounts for.
Lastly, the \texttt{\textless{}filter/\textgreater{}} named
\href{https://liferay.github.io/portletmvc4spring/apidocs/index.html}{\texttt{SpringSecurityPortletFilter}}
prevents Cross-Site Request Forgery (CSRF).
\begin{verbatim}
SpringSecurityPortletFilter
com.liferay.portletmvc4spring.security.SpringSecurityPortletFilter
ACTION_PHASE
RENDER_PHASE
RESOURCE_PHASE
SpringSecurityPortletFilter
portlet1
\end{verbatim}
The
\href{https://docs.liferay.com/portlet-api/3.0/portlet-app_3_0.xsd}{\texttt{portlet\ XSD}}
defines the \texttt{portlet.xml}. The Liferay-specific portlet
descriptor is next.
\section{liferay-portlet.xml}\label{liferay-portlet.xml}
The \texttt{liferay-portlet.xml} file applies Liferay-specific settings
that provide more developer features. Here's an example:
\begin{verbatim}
portlet1
/resources](./images/icon.png
false
administrator
Administrator
guest
Guest
power-user
Power User
user
User
\end{verbatim}
This \texttt{\textless{}portlet/\textgreater{}} element associates an
icon with the portlet and indicates that name-spaced parameters aren't
required.
The \texttt{\textless{}role-mapper/\textgreater{}} elements associate
the portlet with default Liferay DXP user roles.
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-portlet-app_7_2_0.dtd.html}{liferay-portlet-app
DTD} defines the \texttt{liferay-portlet.xml} file.
\section{liferay-display.xml}\label{liferay-display.xml}
The \texttt{liferay-display.xml} applies display characteristics to the
portlet. For example, this descriptor associates the portlet with a
Widget category in Liferay DXP's Add Widget menu.
\begin{verbatim}
\end{verbatim}
See the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-display_7_2_0.dtd.html}{liferay-display
DTD} for details.
It's time to look at the application contexts.
\section{Portlet Application Context}\label{portlet-application-context}
This context applies to all of the application's portlets. This is where
you specify view resolvers, resource bundles, security beans, proxies,
and more. Here's an example:
\begin{verbatim}
content.portlet1
\end{verbatim}
The view resolver bean above handles JSPX view templates. To resolve
Thymeleaf view templates, for example, you could specify these beans:
\begin{verbatim}
\end{verbatim}
The context's \texttt{springSecurityPortletConfigurer} bean facilitates
using Spring Security:
\begin{verbatim}
\end{verbatim}
You can also designate contexts for each portlet in the application.
\section{Portlet Contexts}\label{portlet-contexts}
Beans specific to a portlet, go in the portlet's context. Since
annotations are the easiest way to develop PortletMVC4Spring portlets,
you should specify MVC annotation scanning in the portlet context:
\begin{verbatim}
\end{verbatim}
The portlet context naming convention is
\texttt{{[}portlet-name{]}-context.xml}. To associate your portlet with
its own context, edit your application's \texttt{portlet.xml} file and
add an \texttt{\textless{}init-param/\textgreater{}} element that maps
the \texttt{\textless{}portlet/\textgreater{}} element to the portlet's
context:
\begin{verbatim}
contextConfigLocation
/WEB-INF/spring-context/portlet/portlet1-context.xml
\end{verbatim}
What's left is to describe your application package.
\section{liferay-plugin-package.properties}\label{liferay-plugin-package.properties-1}
This file specifies the application's name, version, Java package
imports/exports, and OSGi metadata. Here's an example package properties
file:
\begin{verbatim}
author=N/A
change-log=
licenses=N/A
liferay-versions=7.1.0+
long-description=
module-group-id=com.mycompany
module-incremental-version=1
name=com.mycompany.my.form.jsp.portlet
page-url=
short-description=my portlet short description
tags=myTag
Bundle-Version: 1.0.0
Import-Package: com.liferay.portal.webserver,com.liferay.portal.kernel.servlet.filters.invoker
\end{verbatim}
It uses this OSGi metadata header to
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{import
required Java packages}:
\begin{verbatim}
Import-Package: com.liferay.portal.webserver,\
com.liferay.portal.kernel.servlet.filters.invoker
\end{verbatim}
On deploying the portlet application WAR file, the
\href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{WAB
Generator} adds the specified OSGi metadata to the resulting web
application bundle (WAB) that's deployed to Liferay's runtime framework.
The
\href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/liferay-plugin-package_7_2_0.properties.html}{liferay-plugin-package
reference document} describes the
\texttt{liferay-plugin-package.properties} file.
Congratulations! You've successfully toured the PortletMVC4Spring
configuration files.
\section{Related Topics}\label{related-topics-48}
\href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-annotations}{PortletMVC4Spring
Annotations}
\href{/docs/7-2/appdev/-/knowledge_base/a/migrating-to-portletmvc4spring}{Migrating
to PortletMVC4Spring}
\chapter{Project Templates}\label{project-templates}
Liferay provides project templates that you can use to generate starter
projects formatted in an opinionated way. These templates can be used by
most build tools (e.g., Gradle, Maven, Dev Studio) to generate your
desired project structure.
If you're using
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI},
execute the following command to display a full list of project
templates:
\begin{verbatim}
blade create -l
\end{verbatim}
If you're using
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Maven}, you can view
and use the project templates as Maven archetypes. Execute the following
command to list them:
\begin{verbatim}
mvn archetype:generate -Dfilter=liferay
\end{verbatim}
Archetypes with the \texttt{com.liferay.project.templates} prefix are
the latest templates offered by Liferay.
If you're using
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Dev
Studio}, navigate to \emph{File} → \emph{New} → \emph{Liferay Module
Project} and view the project templates from the \emph{Project Template
Name} drop-down menu.
In this section of reference articles, each project template is outlined
with the appropriate generation command and folder structure. Visit the
project template article you're most interested in to start building
your own project!
\chapter{Activator Template}\label{activator-template}
In this article, you'll learn how to create a Liferay activator as a
Liferay module. To create a Liferay activator via the command line using
Blade CLI or Maven, use one of the commands with the following
parameters:
\begin{verbatim}
blade create -t activator [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.activator \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{activator}. Suppose you
want to create an activator project called \texttt{my-activator-project}
with a package name of \texttt{com.liferay.docs.activator} and a class
name of \texttt{Activator}. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t activator -p com.liferay.docs.activator -c Activator my-activator-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.activator \
-DgroupId=com.liferay \
-DartifactId=my-activator-project \
-Dpackage=com.liferay.docs.activator \
-Dversion=1.0 \
-DclassName=Activator \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
Note that in your class, you're implementing the
\texttt{org.osgi.framework.BundleActivator} interface.
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-activator-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/activator}
\begin{itemize}
\tightlist
\item
\texttt{Activator.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above.
\chapter{API Template}\label{api-template}
In this tutorial, you'll learn how to create a Liferay API as a Liferay
module. To create a Liferay API via the command line using Blade CLI or
Maven, use one of the commands with the following parameters:
\begin{verbatim}
blade create -t api [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.api \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{api}. The \texttt{api}
template creates a simple \texttt{api} module with an empty public
interface. For example, suppose you want to create an API project called
\texttt{my-api-project} with a package name of
\texttt{com.liferay.docs.api} and a class name of \texttt{MyApi}. You
could run the following command to accomplish this:
\begin{verbatim}
blade create -t api -p com.liferay.docs -c MyApi my-api-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.api \
-DgroupId=com.liferay \
-DartifactId=my-api-project \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=MyApi \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-api-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/api}
\begin{itemize}
\tightlist
\item
\texttt{MyApi.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/api}
\begin{itemize}
\tightlist
\item
\texttt{packageinfo}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is a working application and is deployable to a
Liferay DXP instance. To build upon the generated app, modify the
project by adding logic and additional files to the folders outlined
above.
\chapter{Control Menu Entry Template}\label{control-menu-entry-template}
In this article, you'll learn how to create a Liferay Control Menu entry
as a Liferay module. To create a Liferay Control Menu entry via the
command line using Blade CLI or Maven, use one of the commands with the
following parameters:
\begin{verbatim}
blade create -t control-menu-entry [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.control.menu.entry \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{control-menu-entry}.
Suppose you want to create a control menu entry project called
\texttt{my-control-menu-entry-project} with a package name of
\texttt{com.liferay.docs.entry.control.menu} and a class name of
\texttt{SampleProductNavigationControlMenuEntry}. You could run the
following command to accomplish this:
\begin{verbatim}
blade create -t control-menu-entry -p com.liferay.docs.entry -c Sample my-control-menu-entry-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.control.menu.entry \
-DgroupId=com.liferay \
-DartifactId=my-control-menu-entry-project \
-Dpackage=com.liferay.docs.entry \
-Dversion=1.0 \
-DclassName=Sample \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this:
\begin{itemize}
\tightlist
\item
\texttt{my-control-menu-entry-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/entry/control/menu}
\begin{itemize}
\tightlist
\item
\texttt{SampleProductNavigationControlMenuEntry.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above. You can visit
the
\href{/docs/7-1/reference/-/knowledge_base/r/control-menu-entry}{control-menu-entry}
sample project for a more expanded sample of a Control Menu entry.
\chapter{Form Field Template}\label{form-field-template}
In this article, you'll learn how to create a Liferay form field as a
Liferay module. To create a Liferay form field via the command line
using Blade CLI or Maven, use one of the commands with the following
parameters:
\begin{verbatim}
blade create -t form-field [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.form.field \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{form-field}. Suppose
you want to create a form field project called
\texttt{my-form-field-project} with a package name of
\texttt{com.liferay.docs.form.field} and a class name prefix of
\texttt{MyFormField}. You could run one of the following commands to
accomplish this:
\begin{verbatim}
blade create -t form-field -p com.liferay.docs -c MyFormField my-form-field-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.form.field \
-DgroupId=com.liferay \
-DartifactId=my-form-field-project \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=MyFormField \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-form-field-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/form/field}
\begin{itemize}
\tightlist
\item
\texttt{MyFormFieldDDMFormFieldRenderer.java}
\item
\texttt{MyFormFieldDDMFormFieldType.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{config.js}
\item
\texttt{my-form-field-project.soy}
\item
\texttt{my-form-field-project\_field.js}
\item
\texttt{my-form-field-project\_field.es.js}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is a working form field and is deployable to a
Liferay DXP instance. To build upon the generated app, modify the
project by adding logic and additional files to the folders outlined
above.
\chapter{Fragment Template}\label{fragment-template}
In this article, you'll learn how to create a Liferay fragment as a
Liferay module. You can learn more about fragment modules in the
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-osgi-fragments\#declaring-a-fragment-host}{Declaring
a Fragment Host} article and in section 3.14 of the
\href{https://osgi.org/download/r6/osgi.core-6.0.0.pdf}{OSGi Alliance's
core specification document}.
To create a Liferay fragment via the command line using Blade CLI or
Maven, use one of the commands with the following parameters:
\begin{verbatim}
blade create -t fragment [-h hostBundleName] [-H hostBundleVersion] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.fragment \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{fragment}. Suppose you
want to create a fragment project called \texttt{my-fragment-project}
with a host bundle symbolic name of \texttt{com.liferay.login.web} and
host bundle version of \texttt{1.0.0}. You could run the following
command to accomplish this:
\begin{verbatim}
blade create -t fragment -h com.liferay.login.web -H 1.0.0 my-fragment-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.fragment \
-DgroupId=com.liferay \
-DartifactId=my-fragment-project \
-Dversion=1.0 \
-Dpackage= \
-DhostBundleSymbolicName=com.liferay.login.web \
-DhostBundleVersion=1.0.0 \
-DliferayVersion=7.2
\end{verbatim}
The folder structure is created, but there are no files. The only files
created are the \texttt{bnd.bnd} and \texttt{build.gradle} files, which
specify your host bundle and its information, and your build tool's
files. After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-fragment-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{my/fragment/project} -\texttt{resources}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above.
\chapter{FreeMarker Portlet Template}\label{freemarker-portlet-template}
In this article, you'll learn how to create a Liferay FreeMarker portlet
application as a Liferay module. To create a Liferay FreeMarker portlet
application via the command line using Blade CLI or Maven, use one of
the commands with the following parameters:
\begin{verbatim}
blade create -t freemarker-portlet [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.freemarker.portlet \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{freemarker-portlet}.
Suppose you want to create a FreeMarker portlet project called
\texttt{my-freemarker-portlet-project} with a package name of
\texttt{com.liferay.docs.freemarkerportlet} and a class name of
\texttt{MyFreemarkerPortlet}. Also, you'd like to create a service of
type \texttt{javax.portlet.Portlet} that extends the
\texttt{com.liferay.util.bridges.freemarker.FreeMarkerPortlet} class.
Here, \emph{service} means an OSGi service, not a Liferay API. Another
way to say \emph{service type} is to say \emph{component type}. You
could run the following command to accomplish this:
\begin{verbatim}
blade create -t freemarker-portlet -p com.liferay.docs.freemarkerportlet -c MyFreemarkerPortlet my-freemarker-portlet-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.freemarker.portlet \
-DgroupId=com.liferay \
-DartifactId=my-freemarker-portlet-project \
-Dpackage=com.liferay.docs.freemarkerportlet \
-Dversion=1.0 \
-DclassName=MyFreemarkerPortlet \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-freemarker-portlet-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/freemarkerportlet}
\begin{itemize}
\tightlist
\item
\texttt{constants}
\begin{itemize}
\tightlist
\item
\texttt{MyFreemarkerPortletKeys.java}
\end{itemize}
\item
\texttt{portlet}
\begin{itemize}
\tightlist
\item
\texttt{MyFreemarkerPortlet.java}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{css}
\begin{itemize}
\tightlist
\item
\texttt{main.scss}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{templates}
\begin{itemize}
\tightlist
\item
\texttt{init.ftl}
\item
\texttt{view.ftl}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is a working application and is deployable to a
Liferay DXP instance. To build upon the generated app, modify the
project by adding logic and additional files to the folders outlined
above.
\chapter{Layout Template}\label{layout-template}
In this article, you'll learn how to create a Liferay layout template as
a WAR project. To create a Liferay layout template via the command line
using Blade CLI or Maven, use one of the commands with the following
parameters:
\begin{verbatim}
blade create -t layout-template projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.layout.template \
-DartifactId=[projectName] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{layout-template}.
Suppose you want to create a layout template project called
\texttt{my-layout-template-project}. You could run one of the following
commands to accomplish this:
\begin{verbatim}
blade create -t layout-template my-layout-template-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.layout.template \
-DgroupId=com.liferay \
-DartifactId=my-layout-template-project \
-Dversion=1.0 \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-layout-template-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{webapp}
\begin{itemize}
\tightlist
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{liferay-layout-templates.xml}
\item
\texttt{liferay-plugin-package.properties}
\end{itemize}
\item
\texttt{my-layout-template-project.ftl}
\item
\texttt{my-layout-template-project.png}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated WAR is a working layout template and is deployable to a
Liferay DXP instance. To build upon the generated layout template,
modify the project by adding logic and additional files to the folders
outlined above.
\chapter{Modules Ext Template}\label{modules-ext-template}
In this article, you'll learn how to create an Ext module. To create an
Ext module via the command line using Blade CLI or Maven, use one of the
commands with the following parameters:
\begin{verbatim}
blade create -t modules-ext [-p packageName] [-m originalModuleName] [-M originalModuleVersion] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.modules.ext \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DoriginalModuleName=[originalModuleName] \
-DoriginalModuleVersion=[originalModuleVersion] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{modules-ext}. Suppose
you want to create an Ext module called \texttt{my-ext-module-project}
that overrides the \texttt{com.liferay.test.web} module (BSN) with
version \texttt{1.0.0}. If you have
\href{/docs/7-1/tutorials/-/knowledge_base/t/managing-the-target-platform-for-liferay-workspace}{Target
Platform} enabled, you're not required to specify the intended module
version to override. Also, the override module has a package path of
\texttt{com.liferay.docs.test}. You must use the exact path of the
original module when creating an Ext module. You could run the following
command to accomplish this:
\begin{verbatim}
blade create -t modules-ext -p com.liferay.docs.test -m com.liferay.test.web -M 1.0.0 my-ext-module-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.modules.ext \
-DartifactId=my-ext-module-project \
-Dpackage=com.liferay.docs.test \
-DoriginalModuleName=com.liferay.test.web \
-DoriginalModuleVersion=1.0.0 \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-ext-module-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/test}
\end{itemize}
\item
\texttt{resources}
\end{itemize}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
To build upon the generated project, modify the project by adding logic
and additional files to the folders outlined above.
\chapter{MVC Portlet Template}\label{mvc-portlet-template}
In this article, you'll learn how to create a Liferay MVC portlet
application as a Liferay module. To create a Liferay MVC portlet
application via the command line using Blade CLI or Maven, use one of
the commands with the following parameters:
\begin{verbatim}
blade create -t mvc-portlet [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.mvc.portlet \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{mvc-portlet}. Suppose
you want to create an MVC portlet project called
\texttt{my-mvc-portlet-project} with a package name of
\texttt{com.liferay.docs.mvcportlet} and a class name of
\texttt{MyMvcPortlet}. Also, you'd like to create a service of type
\texttt{javax.portlet.Portlet} that extends the
\texttt{com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet} class.
Here, \emph{service} means an OSGi service, not a Liferay API. Another
way to say \emph{service type} is to say \emph{component type}. You
could run the following command to accomplish this:
\begin{verbatim}
blade create -t mvc-portlet -p com.liferay.docs.mvcportlet -c MyMvcPortlet my-mvc-portlet-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.mvc.portlet \
-DgroupId=com.liferay \
-DartifactId=my-mvc-portlet-project \
-Dpackage=com.liferay.docs.mvcportlet \
-Dversion=1.0 \
-DclassName=MyMvcPortlet \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-mvc-portlet-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/mvcportlet}
\begin{itemize}
\tightlist
\item
\texttt{constants}
\begin{itemize}
\tightlist
\item
\texttt{MyMvcPortletKeys.java}
\end{itemize}
\item
\texttt{portlet}
\begin{itemize}
\tightlist
\item
\texttt{MyMvcPortlet.java}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{init.jsp}
\item
\texttt{view.jsp}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is a working application and is deployable to a
Liferay DXP instance. To build upon the generated app, modify the
project by adding logic and additional files to the folders outlined
above.
\chapter{Panel App Template}\label{panel-app-template}
In this article, you'll learn how to create a Liferay panel app and
category as a Liferay module. To create a Liferay panel app and category
via the command line using Blade CLI or Maven, use one of the commands
with the following parameters:
\begin{verbatim}
blade create -t panel-app [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.panel.app \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{panel-app}. Suppose you
want to create a panel app project called \texttt{my-panel-app-project}
with a package name prefix of \texttt{com.liferay.docs} and a class name
prefix of \texttt{Sample}. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t panel-app -p com.liferay.docs -c Sample my-panel-app-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.panel.app \
-DgroupId=com.liferay \
-DartifactId=my-panel-app-project \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Sample \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this
\begin{itemize}
\tightlist
\item
\texttt{my-panel-app-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/}
\begin{itemize}
\tightlist
\item
\texttt{application/list}
\begin{itemize}
\tightlist
\item
\texttt{SamplePanelApp.java}
\item
\texttt{SamplePanelCategory.java}
\end{itemize}
\item
\texttt{constants}
\begin{itemize}
\tightlist
\item
\texttt{SamplePanelCategoryKeys.java}
\item
\texttt{SamplePortletKeys.java}
\end{itemize}
\item
\texttt{portlet}
\begin{itemize}
\tightlist
\item
\texttt{SamplePortlet.java}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{init.jsp}
\item
\texttt{view.jsp}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. The generated module, by default, creates a panel category
with a panel app in Liferay DXP's Product Menu. To build upon the
generated app, modify the project by adding logic and additional files
to the folders outlined above.
\chapter{Portlet Configuration Icon}\label{portlet-configuration-icon}
In this article, you'll learn how to create a Liferay portlet
configuration icon as a Liferay module. To create a portlet
configuration icon via the command line using Blade CLI or Maven, use
one of the commands with the following parameters:
\begin{verbatim}
blade create -t portlet-configuration-icon [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.portlet.configuration.icon \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is
\texttt{portlet-configuration-icon}. Suppose you want to create a
portlet configuration icon project called
\texttt{my-portlet-config-icon} with a package name of
\texttt{com.liferay.docs.portlet.configuration.icon} and a class name of
\texttt{SamplePortletConfigurationIcon}. You could run the following
command to accomplish this:
\begin{verbatim}
blade create -t portlet-configuration-icon -p com.liferay.docs -c Sample my-portlet-config-icon
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.portlet.configuration.icon \
-DgroupId=com.liferay \
-DartifactId=my-portlet-config-project \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Sample \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this
\begin{itemize}
\tightlist
\item
\texttt{my-portlet-config-icon}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/portlet/configuration/icon}
\begin{itemize}
\tightlist
\item
\texttt{SamplePortletConfigurationIcon.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. The generated module, by default, creates a sample link in the
Hello World portlet's Options menu. To build upon the generated app,
modify the project by adding logic and additional files to the folders
outlined above. You can visit the
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/extensions/portlet-configuration-icon}{portlet-configuration-icon}
sample project for a more expanded sample of a portlet configuration
icon.
\chapter{Portlet Provider Template}\label{portlet-provider-template}
In this article, you'll learn how to create a Liferay portlet provider
as a Liferay module. To create a Liferay portlet provider via the
command line using Blade CLI or Maven, use one of the commands with the
following parameters:
\begin{verbatim}
blade create -t portlet-provider [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.portlet.provider \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{portlet-provider}.
Suppose you want to create a portlet provider project called
\texttt{my-portlet-provider-project} with a package name of
\texttt{com.liferay.docs.portlet} and a class name prefix of
\texttt{Sample}. You could run the following command to accomplish this:
\begin{verbatim}
blade create -t portlet-provider -p com.liferay.docs -c Sample my-portlet-provider-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.portlet.provider \
-DgroupId=com.liferay \
-DartifactId=my-portlet-provider-project \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Sample \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this
\begin{itemize}
\tightlist
\item
\texttt{my-portlet-provider-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs}
\begin{itemize}
\tightlist
\item
\texttt{constants}
\begin{itemize}
\tightlist
\item
\texttt{SamplePortletKeys.java}
\item
\texttt{SampleWebKeys.java}
\end{itemize}
\item
\texttt{portlet}
\begin{itemize}
\tightlist
\item
\texttt{SampleAddPortletProvider.java}
\item
\texttt{SamplePortlet.java}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{init.jsp}
\item
\texttt{view.jsp}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above. You can visit
the
\href{/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes}{Providing
Portlets to Manage Requests} tutorial for instructions on customizing a
portlet provider project.
\chapter{Portlet Toolbar Contributor
Template}\label{portlet-toolbar-contributor-template}
In this article, you'll learn how to create a Liferay portlet toolbar
contributor as a Liferay module. To create a portlet toolbar contributor
entry via the command line using Blade CLI or Maven, use one of the
commands with the following parameters:
\begin{verbatim}
blade create -t portlet-toolbar-contributor [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.portlet.toolbar.contributor \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is
\texttt{portlet-toolbar-contributor}. Suppose you want to create a
portlet toolbar contributor project called
\texttt{my-portlet-toolbar-contributor} with a package name of
\texttt{com.liferay.docs.portlet.toolbar.contributor} and a class name
of \texttt{SamplePortletToolbarContributor}. You could run the following
command to accomplish this:
\begin{verbatim}
blade create -t portlet-toolbar-contributor -p com.liferay.docs -c Sample my-portlet-toolbar-contributor
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.portlet.toolbar.contributor \
-DgroupId=com.liferay \
-DartifactId=my-portlet-toolbar-contributor \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Sample \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this
\begin{itemize}
\tightlist
\item
\texttt{my-portlet-toolbar-contributor}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/portlet/toolbar/contributor}
\begin{itemize}
\tightlist
\item
\texttt{SamplePortletToolbarContributor.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above. This generated
project, by default, creates a new button on the Hello World portlet's
toolbar. You can visit the
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/extensions/portlet-toolbar-contributor}{portlet-toolbar-contributor}
sample project for a more expanded sample of a portlet toolbar
contributor.
\chapter{REST Template}\label{rest-template}
In this article, you'll learn how to create a Liferay RESTful web
service packaged in a Liferay module. To create a Liferay RESTful web
service via the command line using Blade CLI or Maven, use one of the
commands with the following parameters:
\begin{verbatim}
blade create -t rest [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.rest \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{rest}. Suppose you want
to create a RESTful web service project called \texttt{my-rest-project}
with a package name of \texttt{com.liferay.docs.application} and a class
name prefix of \texttt{Rest}. You could run one of the following
commands to accomplish this:
\begin{verbatim}
blade create -t rest -p com.liferay.docs -c Rest my-rest-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.rest \
-DgroupId=com.liferay \
-DartifactId=my-rest-project \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Rest \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-rest-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/application}
\begin{itemize}
\tightlist
\item
\texttt{RestApplication.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{configuration}
\begin{itemize}
\tightlist
\item
\texttt{com.liferay.portal.remote.cxf.common.configuration.CXFEndpointPublisherConfiguration-cxf.properties}
\item
\texttt{com.liferay.portal.remote.rest.extender.configuration.RestExtenderConfiguration-rest.properties}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is a working RESTful web service and is deployable
to a Liferay DXP instance. To build upon the generated app, modify the
project by adding logic and additional files to the folders outlined
above.
\chapter{Service Builder Template}\label{service-builder-template}
In this article, you'll learn how to create a Liferay portlet
application that uses Service Builder as Liferay modules. To create a
Liferay Service Builder project via the command line using Blade CLI or
Maven, use one of the commands with the following parameters:
\begin{verbatim}
blade create -t service-builder [-p packageName] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.service.builder \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DapiPath=[apiPath] \
-DdependencyInjector=[dependencyInjector] \
-DliferayVersion=7.2
\end{verbatim}
By default, the Service Builder project uses OSGi Declarative Services
(\texttt{ds}) for its dependency injector. If you prefer using Spring,
you can set the parameter \texttt{-\/-dependency-injector\ spring} with
Blade CLI or \texttt{-DdependencyInjector=spring} with Maven. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/dependency-injection}{Dependency
Injection} section for more information on these options.
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{service-builder}.
Suppose you want to create a Service Builder project called
\texttt{tasks} with a package name of \texttt{com.liferay.docs.tasks}
using OSGi Declarative Services. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t service-builder -p com.liferay.docs.tasks tasks
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.service.builder \
-DgroupId=com.liferay \
-DartifactId=tasks \
-Dpackage=com.liferay.docs.tasks \
-Dversion=1.0 \
-DapiPath=com.liferay.api.path \
-DdependencyInjector=ds \
-DliferayVersion=7.2
\end{verbatim}
This task creates the \texttt{tasks-api} and \texttt{tasks-service}
folders. In many cases, a Service Builder project also requires a
\texttt{-web} folder to hold, for example, portlet classes. This should
be created manually. After running the Blade command above, your
project's directory structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{tasks}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{tasks-api}
\begin{itemize}
\tightlist
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\end{itemize}
\item
\texttt{tasks-service}
\begin{itemize}
\tightlist
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{service.xml}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\item
\texttt{settings.gradle}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
To generate your service and API classes for the \texttt{*-api} and
\texttt{*-service} modules, replace the \texttt{service.xml} file in the
\texttt{*-service} module. Depending on your build tool, you can build
your services by executing
\begin{verbatim}
blade gw buildService
\end{verbatim}
or
\begin{verbatim}
mvn service-builder:build
\end{verbatim}
from the \texttt{tasks} root directory. Note that \texttt{blade\ gw}
only works if the Gradle wrapper can be detected. To ensure the
availability of the Gradle wrapper, be sure to work in a Liferay
Workspace.
The \texttt{mvn\ service-builder:build} command only works if you're
using the \texttt{com.liferay.portal.tools.service.builder} plugin
version 1.0.145+. Maven projects using an earlier version of the Service
Builder plugin should update their POM accordingly.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above.
For more information on Service Builder, see the
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} section.
\chapter{Service Template}\label{service-template}
In this article, you'll learn how to create a Liferay service as a
Liferay module. To create a Liferay service via the command line using
Blade CLI or Maven, use one of the commands with the following
parameters:
\begin{verbatim}
blade create -t service [-p packageName] [-c className] [-s serviceName] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.service \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className]
-DserviceName=[serviceName] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{service}. Suppose you
want to create a service project called \texttt{my-service-project} with
a package name of \texttt{com.liferay.docs.service} and a class name of
\texttt{Service}. Also, you'd like to create a service of type
\texttt{com.liferay.portal.kernel.events.LifecycleAction} that also
implements that same service. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t service -p com.liferay.docs.service -c Service -s com.liferay.portal.kernel.events.LifecycleAction my-service-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.service \
-DgroupId=com.liferay \
-DartifactId=my-service-project \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Service \
-DclassName=com.liferay.portal.kernel.events.LifecycleAction \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this
\begin{itemize}
\tightlist
\item
\texttt{my-service-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/service}
\begin{itemize}
\tightlist
\item
\texttt{Service.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above.
\chapter{Service Wrapper Template}\label{service-wrapper-template}
In this article, you'll learn how to create a Liferay service wrapper as
a Liferay module. To create a Liferay service wrapper via the command
line using Blade CLI or Maven, use one of the commands with the
following parameters:
\begin{verbatim}
blade create -t service-wrapper [-p packageName] [-c className] [-s serviceWrapperClass] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.service.wrapper \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DserviceWrapperClass=[serviceWrapperClass] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{service-wrapper}.
Suppose you want to create a service wrapper project called
\texttt{service-override} with a package name of
\texttt{com.liferay.docs.serviceoverride} and a class name of
\texttt{UserLocalServiceOverride}. Also, you'd like to create a service
of type \texttt{com.liferay.portal.kernel.service.ServiceWrapper} that
extends the \texttt{com.liferay.portal.service.UserLocalServiceWrapper}
class. You could run the following command to accomplish this:
\begin{verbatim}
blade create -t service-wrapper -p com.liferay.docs.serviceoverride -c UserLocalServiceOverride -s com.liferay.portal.kernel.service.UserLocalServiceWrapper service-override
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.service.wrapper \
-DgroupId=com.liferay \
-DartifactId=service-override \
-Dpackage=com.liferay.docs.serviceoverride \
-Dversion=1.0 \
-DclassName=UserLocalServiceOverride \
-DserviceWrapperClass=com.liferay.portal.kernel.service.UserLocalServiceWrapper \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
Here, \emph{service} means an OSGi service, not a Liferay API. Another
way to say \emph{service type} is to say \emph{component type}.
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{service-override}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/serviceoverride}
\begin{itemize}
\tightlist
\item
\texttt{UserLocalServiceOverride.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is a working application and is deployable to a
Liferay DXP instance. To build upon the generated app, modify the
project by adding logic and additional files to the folders outlined
above.
\chapter{Simulation Panel Entry
Template}\label{simulation-panel-entry-template}
In this article, you'll learn how to create a Liferay simulation panel
entry as a Liferay module. To create a simulation panel entry via the
command line using Blade CLI or Maven, use one of the commands with the
following parameters:
\begin{verbatim}
blade create -t simulation-panel-entry [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.simulation.panel.entry \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is
\texttt{simulation-panel-entry}. Suppose you want to create a simulation
panel entry project called \texttt{my-simulation-panel-entry} with a
package name of \texttt{com.liferay.docs.application.list} and a class
name of \texttt{SampleSimulationPanelApp}. You could run the following
command to accomplish this:
\begin{verbatim}
blade create -t simulation-panel-entry -p com.liferay.docs -c Sample my-simulation-panel-entry
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.simulation.panel.entry \
-DgroupId=com.liferay \
-DartifactId=my-simulation-panel-entry \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Sample \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this
\begin{itemize}
\tightlist
\item
\texttt{my-simulation-panel-entry}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/application/list}
\begin{itemize}
\tightlist
\item
\texttt{SampleSimulationPanelApp.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{simulation\_panel.jsp}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above. You can visit
the
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/apps/simulation-panel-app}{simulation-panel-app}
sample project for a more expanded sample of a control menu entry.
\chapter{Social Bookmark Template}\label{social-bookmark-template}
In this article, you'll learn how to create a Liferay social bookmark as
a Liferay module. To create a social bookmark as a module via the
command line using Blade CLI or Maven, use one of the commands with the
following parameters:
\begin{verbatim}
blade create -t social-bookmark [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.social.bookmark \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{social-bookmark}.
Suppose you want to create a social bookmark project called
\texttt{my-social-bookmark-project} with a package name of
\texttt{com.liferay.docs.socialbookmark} and a class name of
\texttt{TestSocialBookmark}. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t social-bookmark -p com.liferay.docs.socialbookmark -c Test my-social-bookmark-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.social.bookmark \
-DgroupId=com.liferay \
-DartifactId=my-social-bookmark-project \
-Dpackage=com.liferay.docs.socialbookmark \
-Dversion=1.0 \
-DclassName=Test \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-social-bookmark-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/socialbookmark}
\begin{itemize}
\tightlist
\item
\texttt{social/bookmark}
\begin{itemize}
\tightlist
\item
\texttt{TestSocialBookmark}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{icons.svg}
\item
\texttt{init.jsp}
\item
\texttt{page.jsp}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is a working application and is deployable to a
Liferay DXP instance. An unmodified module generated as described above
creates a social bookmark named \emph{Test} that searches the current
URL using Google Search.
\begin{figure}
\centering
\includegraphics{./images/social-bookmark-project-template.png}
\caption{Click the magnifying glass icon to search the current URL using
Google Search.}
\end{figure}
To build upon the generated app, modify the project by adding logic and
additional files to the folders outlined above. For more information on
developing social bookmarks, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/social-api}{Social API}
section of tutorials. For information on configuring social bookmarks
for the Blogs widget, see the
\href{/docs/7-2/user/-/knowledge_base/u/displaying-blogs}{Displaying
Blogs} article.
\chapter{Spring MVC Portlet Template}\label{spring-mvc-portlet-template}
In this article, you'll learn how to create a Liferay Spring MVC portlet
application as a WAR. To create a Liferay Spring MVC portlet via the
command line using Blade CLI or Maven, use one of the commands with the
following parameters:
\begin{verbatim}
blade create -t spring-mvc-portlet [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.spring.mvc.portlet \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{spring-mvc-portlet}.
Suppose you want to create a Spring MVC portlet project called
\texttt{my-spring-mvc-portlet-project} with a package name of
\texttt{com.liferay.docs.springmvcportlet} and a class name of
\texttt{MySpringMvcPortlet}. Also, you'd like to create a
Spring-annotated portlet class named
\texttt{MySpringMvcPortletViewController}.
\begin{verbatim}
blade create -t spring-mvc-portlet -p com.liferay.docs.springmvcportlet -c MySpringMvcPortlet my-spring-mvc-portlet-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.spring.mvc.portlet \
-DgroupId=com.liferay \
-DartifactId=my-spring-mvc-portlet-project \
-Dpackage=com.liferay.docs.springmvcportlet \
-Dversion=1.0 \
-DclassName=MySpringMvcPortlet \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-spring-mvc-portlet-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/springmvcportlet/portlet}
\begin{itemize}
\tightlist
\item
\texttt{MySpringMvcPortletViewController}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\end{itemize}
\item
\texttt{webapp}
\begin{itemize}
\tightlist
\item
\texttt{css}
\begin{itemize}
\tightlist
\item
\texttt{main.scss}
\end{itemize}
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{jsp}
\begin{itemize}
\tightlist
\item
\texttt{init.jsp}
\item
\texttt{view.jsp}
\end{itemize}
\item
\texttt{spring-context}
\begin{itemize}
\tightlist
\item
\texttt{portlet}
\begin{itemize}
\tightlist
\item
\texttt{my-spring-mvc-portlet-project.xml}
\end{itemize}
\item
\texttt{portlet-application-context.xml}
\end{itemize}
\item
\texttt{tld}
\begin{itemize}
\tightlist
\item
\texttt{liferay-portlet.tld}
\item
\texttt{liferay-portlet-ext.tld}
\item
\texttt{liferay-security.tld}
\item
\texttt{liferay-theme.tld}
\item
\texttt{liferay-ui.tld}
\item
\texttt{liferay-util.tld}
\end{itemize}
\item
\texttt{liferay-display.xml}
\item
\texttt{liferay-plugin-package.properties}
\item
\texttt{liferay-portlet.xml}
\item
\texttt{portlet.xml}
\item
\texttt{web.xml}
\end{itemize}
\item
\texttt{icon.png}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated WAR is a working application and is deployable to a
Liferay DXP instance. To build upon the generated app, modify the
project by adding logic and additional files to the folders outlined
above. You can visit the
\href{/docs/7-1/reference/-/knowledge_base/r/spring-mvc-portlet}{springmvc-portlet}
sample project for a more expanded sample of a Spring MVC portlet.
\chapter{Template Context Contributor
Template}\label{template-context-contributor-template}
In this article, you'll learn how to create a Liferay template context
contributor as a Liferay module. To create a template context
contributor via the command line using Blade CLI or Maven, use one of
the commands with the following parameters:
\begin{verbatim}
blade create -t template-context-contributor [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.template.context.contributor \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is
\texttt{template-context-contributor}. Suppose you want to create a
template context contributor project called
\texttt{my-template-context-contributor} with a package name of
\texttt{com.liferay.docs.theme.contributor} and a class name of
\texttt{SampleTemplateContextContributor}. You could run the following
command to accomplish this:
\begin{verbatim}
blade create -t template-context-contributor -p com.liferay.docs -c Sample my-template-context-contributor
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.template.context.contributor \
-DgroupId=com.liferay \
-DartifactId=my-template-context-contributor \
-Dpackage=com.liferay.docs \
-Dversion=1.0 \
-DclassName=Sample \
-Dauthor=Joe Bloggs \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's directory
structure would look like this
\begin{itemize}
\tightlist
\item
\texttt{my-template-context-contributor}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/context/contributor}
\begin{itemize}
\tightlist
\item
\texttt{SampleTemplateContextContributor.java}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above. You can visit
the
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/themes/template-context-contributor}{template-context-contributor}
sample project for a more expanded sample of a template context
contributor. Likewise, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/injecting-additional-context-variables-and-functionality-into-your-theme-templates}{Context
Contributors} tutorial for instructions on customizing a template
context contributor project.
\chapter{Theme Contributor Template}\label{theme-contributor-template}
In this article, you'll learn how to create a Liferay theme contributor
as a Liferay module. To create a theme contributor via the command line
using Blade CLI or Maven, use one of the commands with the following
parameters:
\begin{verbatim}
blade create -t theme-contributor [--contributorType contributorType] [-p packageName] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.theme.contributor \
-DartifactId=[projectName] \
-Dpackage=[packageName] \
-DcontributorType=[contributorType] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{theme-contributor}.
Suppose you want to create a theme contributor project called
\texttt{my-theme-contributor} with a package name of
\texttt{com.liferay.docs.theme.contributor} and a contributor type of
\texttt{my-contributor}. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t theme-contributor --contributor-type my-contributor -p com.liferay.docs.theme.contributor my-theme-contributor
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.theme.contributor \
-DgroupId=com.liferay \
-DartifactId=my-theme-contributor \
-Dpackage=com.liferay.docs.theme.contributor \
-Dversion=1.0 \
-DcontributorType=my-contributor \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's folder structure
would look like this:
\begin{itemize}
\tightlist
\item
\texttt{my-theme-contributor}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/theme/contributor}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{css}
\begin{itemize}
\tightlist
\item
\texttt{my-contributor}
\begin{itemize}
\tightlist
\item
\texttt{\_body.scss}
\item
\texttt{\_control\_menu.scss}
\item
\texttt{\_product\_menu.scss}
\item
\texttt{\_simulation\_panel.scss}
\end{itemize}
\item
\texttt{my-contributor.scss}
\end{itemize}
\item
\texttt{js}
\begin{itemize}
\tightlist
\item
\texttt{my-contributor.js}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated module is functional and is deployable to a Liferay DXP
instance. To build upon the generated app, modify the project by adding
logic and additional files to the folders outlined above. You can visit
the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-contributor}{Blade
Theme Contributor} sample project for a more expanded sample of a theme
contributor. Likewise, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site}{Theme
Contributors} tutorial for instructions on customizing a theme
contributor project.
\chapter{Theme Template}\label{theme-template}
In this article, you'll learn how to create a Liferay theme as a WAR
project. To create a Liferay theme via the command line using Blade CLI
or Maven, use one of the commands with the following parameters:
\begin{verbatim}
blade create -t theme projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.theme \
-DartifactId=[projectName] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{theme}. Suppose you
want to create a theme project called \texttt{my-theme-project} as a WAR
file. You could run the following command to accomplish this:
\begin{verbatim}
blade create -t theme my-theme-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.theme \
-DgroupId=com.liferay \
-DartifactId=my-theme-project \
-Dversion=1.0 \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's folder structure
looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-theme-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{resources-importer}
\begin{itemize}
\tightlist
\item
\texttt{sitemap.json}
\end{itemize}
\end{itemize}
\item
\texttt{webapp}
\begin{itemize}
\tightlist
\item
\texttt{css}
\begin{itemize}
\tightlist
\item
\texttt{\_custom.scss}
\end{itemize}
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{liferay-plugin-package.properties}
\item
\texttt{web.xml}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated theme is functional and is deployable to a Liferay DXP
instance. To build upon the generated project, modify the project by
adding logic and additional files to the folders outlined above. You can
visit the
\href{/docs/7-1/reference/-/knowledge_base/r/theme}{simple-theme}
project for a more expanded sample of a theme. Likewise, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes}
section for more information on creating themes.
\chapter{WAR Core Ext}\label{war-core-ext}
In this article, you'll learn how to create a Liferay WAR core Ext
project. To create a WAR core Ext project via the command line using
Blade CLI or Maven, use one of the commands with the following
parameters:
\begin{verbatim}
blade create -t war-core-ext projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.war.core.ext \
-DartifactId=[projectName] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{war-core-ext}. Suppose
you want to create a WAR core Ext project called
\texttt{my-war-core-ext-project}. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t war-core-ext my-war-core-ext-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.war.core-ext \
-DgroupId=com.liferay \
-DartifactId=my-war-core-ext-project \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's folder structure
looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-war-core-ext-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{extImpl}
\begin{itemize}
\tightlist
\item
\texttt{java}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{ext-model-hints.xml}
\item
\texttt{ext-spring.xml}
\item
\texttt{portal-log4j-ext.xml}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{extKernel}
\begin{itemize}
\tightlist
\item
\texttt{java}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\end{itemize}
\end{itemize}
\item
\texttt{extUtilBridges}
\begin{itemize}
\tightlist
\item
\texttt{java}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\end{itemize}
\end{itemize}
\item
\texttt{extUtilJava}
\begin{itemize}
\tightlist
\item
\texttt{java}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\end{itemize}
\end{itemize}
\item
\texttt{extUtilTaglib}
\begin{itemize}
\tightlist
\item
\texttt{java}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\end{itemize}
\end{itemize}
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{webapp}
\begin{itemize}
\tightlist
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{ext-web}
\begin{itemize}
\tightlist
\item
\texttt{docroot}
\begin{itemize}
\tightlist
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{liferay-portlet-ext.xml}
\item
\texttt{portlet-ext.xml}
\item
\texttt{struts-config-ext.xml}
\item
\texttt{tiles-defs-ext.xml}
\item
\texttt{web.xml}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{liferay-plugin-package.properties}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
\noindent\hrulefill
\textbf{Note:} If you generate a WAR Ext project using Gradle outside of
Liferay Workspace, you must set the \texttt{app.server.parent.dir}
property in the project's \texttt{gradle.properties}. The app server
location is required for this project to compile.
\noindent\hrulefill
The generated WAR Ext project is functional and is deployable to a
Liferay DXP instance. To build upon the generated project, modify the
project by adding logic and additional files to the folders outlined
above. Deploying WAR Ext projects is only supported for limited use
cases; it is recommended to leverage provided extension points offered
in Liferay DXP. You can visit the
\href{/docs/7-2/customization/-/knowledge_base/c/customization-with-ext}{Customization
with Ext} section for info on how to do this.
\chapter{WAR Hook Template}\label{war-hook-template}
In this article, you'll learn how to create a Liferay WAR hook project.
To create a Liferay WAR hook via the command line using Blade CLI or
Maven, use one of the commands with the following parameters:
\begin{verbatim}
blade create -t war-hook [-p packageName] [-c className] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.war.hook \
-DartifactId=[projectName]
-Dpackage=[packageName] \
-DclassName=[className] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{war-hook}. Suppose you
want to create a WAR hook project called \texttt{my-war-hook-project}
with a package name of \texttt{com.liferay.docs} and a class name of
\texttt{MyWarHook}. You could run the following command to accomplish
this:
\begin{verbatim}
blade create -t war-hook -p com.liferay.docs -c MyWarHook my-war-hook-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.war.hook \
-DgroupId=com.liferay \
-DartifactId=my-war-hook-project \
-Dpackage=com.liferay.docs \
-DclassName=MyWarHook \
-Dversion=1.0 \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's folder structure
looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-war-hook-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs}
\begin{itemize}
\tightlist
\item
\texttt{MyWarHookLoginPostAction}
\item
\texttt{MyWarHookStartupAction}
\end{itemize}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{portal.properties}
\end{itemize}
\item
\texttt{webapp}
\begin{itemize}
\tightlist
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{liferay-hook.xml}
\item
\texttt{liferay-plugin-package.properties}
\item
\texttt{web.xml}
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated WAR hook is functional and is deployable to a Liferay DXP
instance. To build upon the generated project, modify the project by
adding logic and additional files to the folders outlined above.
Deploying WAR hooks is supported for 7.0, however, it is recommended to
optimize your WAR hooks to fragments or other applicable module
projects. You can visit the
\href{/docs/7-2/customization/-/knowledge_base/c/liferay-customization}{Liferay
Customization} section for info on how to do this for many project
types. See the
\href{/docs/6-2/tutorials/-/knowledge_base/t/customizing-liferay-portal}{Customizing
Liferay Portal} section for more information on WAR hooks.
\chapter{WAR MVC Portlet Template}\label{war-mvc-portlet-template}
In this article, you'll learn how to create a Liferay MVC portlet
project as a WAR file. To create a Liferay MVC portlet project as a WAR
via the command line using Blade CLI or Maven, use one of the commands
with the following parameters:
\begin{verbatim}
blade create -t war-mvc-portlet [-p packageName] projectName
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.war.mvc.portlet \
-DartifactId=[projectName]
-Dpackage=[packageName] \
-DliferayVersion=7.2
\end{verbatim}
You can also insert the \texttt{-b\ maven} parameter in the Blade
command to generate a Maven project using Blade CLI.
The template for this kind of project is \texttt{war-mvc-portlet}.
Suppose you want to create a WAR MVC portlet project called
\texttt{my-war-mvc-portlet-project} with a package name of
\texttt{com.liferay.docs.war.mvc} and a class name of
\texttt{MyWarMvcPortlet}. You could run the following command to
accomplish this:
\begin{verbatim}
blade create -t war-mvc-portlet -p com.liferay.docs.war.mvc my-war-mvc-portlet-project
\end{verbatim}
or
\begin{verbatim}
mvn archetype:generate \
-DarchetypeGroupId=com.liferay \
-DarchetypeArtifactId=com.liferay.project.templates.war.mvc.portlet \
-DgroupId=com.liferay \
-DartifactId=my-war-mvc-portlet-project \
-Dpackage=com.liferay.docs.war.mvc \
-Dversion=1.0 \
-DliferayVersion=7.2
\end{verbatim}
After running the Blade command above, your project's folder structure
looks like this:
\begin{itemize}
\tightlist
\item
\texttt{my-war-mvc-portlet-project}
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\begin{itemize}
\tightlist
\item
\texttt{wrapper}
\begin{itemize}
\tightlist
\item
\texttt{gradle-wrapper.jar}
\item
\texttt{gradle-wrapper.properties}
\end{itemize}
\end{itemize}
\item
\texttt{src}
\begin{itemize}
\tightlist
\item
\texttt{main}
\begin{itemize}
\tightlist
\item
\texttt{java}
\begin{itemize}
\tightlist
\item
\texttt{com/liferay/docs/war/mvc}
\end{itemize}
\item
\texttt{resources}
\begin{itemize}
\tightlist
\item
\texttt{content}
\begin{itemize}
\tightlist
\item
\texttt{Language.properties}
\end{itemize}
\end{itemize}
\item
\texttt{webapp}
\begin{itemize}
\tightlist
\item
\texttt{css}
\begin{itemize}
\tightlist
\item
\texttt{main.scss}
\end{itemize}
\item
\texttt{WEB-INF}
\begin{itemize}
\tightlist
\item
\texttt{tld}
\begin{itemize}
\tightlist
\item
\texttt{liferay-portlet.tld}
\item
\texttt{liferay-portlet-ext.tld}
\item
\texttt{liferay-security.tld}
\item
\texttt{liferay-theme.tld}
\item
\texttt{liferay-ui.tld}
\item
\texttt{liferay-util.tld}
\end{itemize}
\item
\texttt{liferay-display.xml}
\item
\texttt{liferay-plugin-package.properties}
\item
\texttt{liferay-portlet.xml}
\item
\texttt{portlet.xml}
\item
\texttt{web.xml}
\end{itemize}
\item
\texttt{init.jsp}
\item
\texttt{view.jsp}
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{build.gradle}
\item
\texttt{gradlew}
\end{itemize}
\end{itemize}
The Maven-generated project includes a \texttt{pom.xml} file and does
not include the Gradle-specific files, but otherwise, appears exactly
the same.
The generated WAR MVC portlet is functional and is deployable to a
Liferay DXP instance. To build upon the generated project, modify the
project by adding logic and additional files to the folders outlined
above. Deploying WAR MVC portlets is supported for 7.0, however, it is
recommended to optimize your WAR portlet to a module project, if
possible. You can visit the
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{From
Liferay Portal 6 to 7} section for info on how to do this.
\chapter{Sample Projects}\label{sample-projects}
\noindent\hrulefill
\textbf{Note:} This section of articles does not provide documentation
for \emph{all} sample projects residing in the
\texttt{liferay-blade-samples} repo. The documentation for these samples
is in progress and will grow over time.
\noindent\hrulefill
Liferay provides sample projects that target different integration
points in Liferay DXP. These projects reside in the
\href{https://github.com/liferay/liferay-blade-samples}{liferay-blade-samples}
Github repository and can be easily copy/pasted to your local
environment. You can also generated them using
\href{/docs/7-2/reference/-/knowledge_base/r/generating-project-samples-with-blade-cli}{Blade
CLI}.
The sample projects are grouped into three different parent folders
based on the build tools used to generate them:
\begin{itemize}
\tightlist
\item
\texttt{gradle}
\item
\texttt{liferay-workspace}
\item
\texttt{maven}
\end{itemize}
The provided sample projects are organized by their development
toolchains to cater to a variety of developers. Each folder offers the
same set of sample Liferay projects. Their only difference is that the
build files are specific to their toolchain. For example, the
\texttt{gradle} folder contains projects using standard OSS Gradle
plugins that can be added to any Gradle composite build. The same
concept also applies to the \texttt{liferay-workspace} and
\texttt{maven} projects.
\noindent\hrulefill
\textbf{Note:} The Liferay Workspace folder stores WAR-type samples in a
separate folder named
\href{https://github.com/liferay/liferay-blade-samples/tree/7.1/liferay-workspace/wars}{wars}.
The Gradle and Maven tool folders mix WAR samples with the other sample
types (apps, extensions, etc.).
\noindent\hrulefill
The \texttt{gradle} folder also uses the Liferay Gradle plugin (e.g.,
\texttt{com.liferay.plugin}) which encompasses additional functionality
for various types of Liferay projects. The Liferay Gradle plugin is
recommended for Gradle users developing for Liferay DXP.
Some samples also come configured with logging to help you fully
understand what the sample is accomplishing behind the scenes. For
example, OSGi module logging is implemented for several samples (e.g.,
\href{https://github.com/liferay/liferay-blade-samples/tree/7.1/gradle/apps/action-command-portlet}{action-command-portlet},
\href{/docs/7-2/reference/-/knowledge_base/r/document-action}{document-action},
\href{/docs/7-2/reference/-/knowledge_base/r/service-builder-application-using-external-database-via-jdbc}{service-builder/jdbc},
etc.), which lets OSGi modules supply their own logging configuration
defaults without external configuration. See the
\href{/docs/7-2/appdev/-/knowledge_base/a/adjusting-module-logging}{Adjusting
Module Logging} article for more information.
For a list of sample template projects available, visit the
\href{https://github.com/liferay/liferay-blade-samples\#liferay-extension-points-and-template-projects}{Liferay
extension points} sub-section in the Liferay Blade Samples repository.
This list is not comprehensive. A subset of missing extension point
samples can be found in the
\href{https://github.com/liferay/liferay-blade-samples\#liferay-extension-points-without-template-projects}{Liferay
extension points without template projects} sub-section. Visit the
repo's
\href{https://github.com/liferay/liferay-blade-samples\#contribution-guidelines}{Contribution
Guidelines} section for details on contributing to this repository.
\chapter{Apps}\label{apps}
This section focuses on Liferay sample applications. You can view these
sample apps by visiting the \texttt{apps} folder corresponding to your
preferred build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps}{Gradle
sample apps}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/apps}{Liferay
Workspace sample apps}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/apps}{Maven
sample apps}
\end{itemize}
Visit a particular sample page to learn more!
\chapter{Service Builder Samples}\label{service-builder-samples}
This section focuses on Liferay Service Builder sample projects built
with various build tools. You can view these samples by visiting the
\texttt{apps/service-builder} folder corresponding to your preferred
build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/service-builder}{Gradle
Service Builder sample apps}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/apps/service-builder}{Liferay
Service Builder Workspace sample apps}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/apps/service-builder}{Maven
Service Builder sample apps}
\end{itemize}
The following Service Builder samples are documented:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/service-builder-application-demonstrating-actionable-dynamic-query}{Service
Builder application demonstrating Actionable Dynamic Query}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/service-builder-application-using-external-database-via-jdbc}{Service
Builder application with JDBC connection}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/service-builder-application-using-external-database-via-jndi}{Service
Builder application with JNDI connection}
\end{itemize}
Visit a particular sample page to learn more!
\chapter{Service Builder Application Demonstrating Actionable Dynamic
Query}\label{service-builder-application-demonstrating-actionable-dynamic-query}
This sample is similar to the
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/service-builder/basic}{\texttt{basic}
Service Builder sample}, which lets you perform CRUD (create, read,
update, delete) operations on service builder entities. The distinctive
feature of the Service Builder Actionable Dynamic Query (ADQ) sample is
that it also lets you perform a mass update on all existing service
builder entities.
\begin{figure}
\centering
\includegraphics{./images/adq-sample.png}
\caption{This sample provides options to add entities and perform a mass
update.}
\end{figure}
To see the ADQ Service Builder sample in action, complete the following
steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the sample to a page by navigating to \emph{Add}
(\includegraphics{./images/icon-add.png}) → \emph{Widgets} →
\emph{Sample} and dragging it to the page.
\item
Select the app's \emph{Add} button and add an entity. Do this several
times to create multiple entities.
\item
Click the \emph{Mass Update} button and click \emph{Save} to invoke
the update.
After invoking the update, each entity's \texttt{field3} value (whose
value is less than 100) is incremented.
\end{enumerate}
You've leveraged the actionable dynamic query API in your sample!
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight}
This sample demonstrates Liferay DXP's actionable dynamic query API.
Specifically, it demonstrates how to create an ADQ, add criteria to an
ADQ, specify an action for the ADQ, and execute the ADQ.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component}
An action request is sent to the \texttt{JSPPortlet} with a \texttt{cmd}
request parameter. When the \texttt{JSPPortlet}'s \texttt{processAction}
method processes the request, the value of the \texttt{cmd} parameter is
parsed and then the portlet's \texttt{massUpdate} method is invoked. The
\texttt{massUpdate} method, in turn, invokes the \texttt{massUpdate}
method defined in the \texttt{adq-service} module's
\texttt{BarLocalServiceImpl}. This is where the sample leverages the
actionable dynamic query API:
\begin{verbatim}
public void massUpdate() {
ActionableDynamicQuery adq = getActionableDynamicQuery();
adq.setAddCriteriaMethod(
new ActionableDynamicQuery.AddCriteriaMethod() {
@Override
public void addCriteria(DynamicQuery dynamicQuery) {
dynamicQuery.add(RestrictionsFactoryUtil.lt("field3", 100));
}
});
adq.setPerformActionMethod(
new ActionableDynamicQuery.PerformActionMethod() {
@Override
public void performAction(Bar bar) {
int field3 = bar.getField3();
field3++;
bar.setField3(field3);
updateBar(bar);
}
});
try {
adq.performActions();
}
catch (Exception e) {
e.printStackTrace();
}
}
\end{verbatim}
For more information on the actionable dynamic query API, visit its
dedicated
\href{/docs/7-0/tutorials/-/knowledge_base/t/dynamic-query\#actionable-dynamic-queries}{tutorial}.
\chapter{Service Builder Application Using External Database via
JDBC}\label{service-builder-application-using-external-database-via-jdbc}
This sample demonstrates how to connect a Liferay Service Builder
application to an external database via a JDBC connection. Here, an
external database means any database other than Liferay DXP's database.
For this sample to work correctly, you must prepare such an external
database and configure Liferay DXP to use it. Follow the steps below to
make the required preparations before deploying the application.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create the external database to which your Service Builder application
will connect. For example, create a MariaDB database called
\texttt{external}. Add a table to this database called
\texttt{country} with a \texttt{BIGINT} column called \texttt{Id} and
a \texttt{VARCHAR(255)} column called \texttt{Name}. Add at least one
record to this table. Here are the MariaDB commands to accomplish
this:
\begin{verbatim}
create database external character set utf8;
use external;
create table country(id bigint not null primary key, name varchar(255));
insert into country(id, name) values(1, 'Australia');
\end{verbatim}
Make sure that your database commands were successful: Running
\texttt{select\ *\ from\ country;} should return the record you added.
\item
Create a \texttt{portal-ext.properties} file in your Liferay DXP
instance's \texttt{{[}LIFERAY\_HOME{]}} folder (this folder should be
marked by the presence of a \texttt{.liferay-home} file). In your
\texttt{portal-ext.properties} file, define the details of your JDBC
data source connection:
\begin{verbatim}
jdbc.ext.driverClassName=org.mariadb.jdbc.Driver
jdbc.ext.password=userpassword
jdbc.ext.url=jdbc:mariadb://localhost/external?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
jdbc.ext.username=yourusername
\end{verbatim}
Note that Liferay DXP's primary data source is specified by the
\texttt{jdbc.default} prefix. These details are often specified in a
\texttt{portal-setup-wizard.properties} file. Here, we've chosen to
use the \texttt{jdbc.ext} prefix for our alternate data source.
\item
Create a
\texttt{com.liferay.blade.samples.jdbcservicebuilder.service-log4j-ext.xml}
in your Liferay instance's \texttt{{[}LIFERAY\_HOME{]}/osgi/log4}
folder. Create this folder if it doesn't yet exist. Add this content
to the XML file that you created:
\begin{verbatim}
\end{verbatim}
This XML file defines the log level for the classes in the
\texttt{com.liferay.blade.samples.jdbcservicebuilder.service.impl}
package. The
\texttt{com.liferay.blade.samples.jdbcservicebuilder.service.impl.CountryLocalServiceImpl}
is the class that will produce log messages when the sample portlet is
viewed.
\end{enumerate}
Now your sample is ready for deployment! Make sure to build and deploy
each of the three modules that comprise the sample application:
\begin{itemize}
\tightlist
\item
\texttt{jdbc-api}
\item
\texttt{jdbc-service}
\item
\texttt{jdbc-web}
\end{itemize}
After these modules have been deployed, add the \texttt{-web} portlet to
a Liferay DXP page.
\begin{figure}
\centering
\includegraphics{./images/jdbc-sb-sample.png}
\caption{This sample prints out the values previously inputted into the
database.}
\end{figure}
A sample table is printed in the portlet's view, representing the info
inputted into the database.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-1}
The sample configures the data source using Spring Beans and
demonstrates two ways to access data from an external database defined
by a JDBC connection:
\begin{itemize}
\tightlist
\item
extract data directly from the raw data source by explicitly
specifying a SQL query.
\item
read data using the helper methods that Service Builder generates in
your application's persistence layer.
\end{itemize}
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-1}
Once you've added the \texttt{-web} portlet to a page, the
\texttt{CountryLocalService.useJDBC} method is invoked. This method
accesses the database defined by the JDBC connection you specified and
logs information about the rows in the \texttt{country} table to Liferay
DXP's log.
\section{Configuring the Data Source}\label{configuring-the-data-source}
The \texttt{-service} module's
\texttt{src/main/resources/META-INF/spring/ext-spring.xml} file
configures the external data source connection and applies the alias
\texttt{extDataSource} to the data source. The \texttt{service.xml} file
\texttt{entity} element specifies the data source via the attribute
assignment \texttt{data-source="extDataSource"}. The
\texttt{ext-spring.xml} and \texttt{service.xml} files demonstrate the
configuration steps explained in
\href{/docs/7-2/appdev/-/knowledge_base/a/connecting-the-data-source-using-spring-beans}{Connecting
the Data Source Using Spring Beans}.
\section{Accessing Data}\label{accessing-data}
The first way of accessing data from the external database is to extract
it directly from the raw data source by explicitly specifying a SQL
query. This technique is demonstrated by the
\texttt{CountryLocalServiceImpl.useJDBC} method. That method obtains the
Spring-defined data source that's injected into the
\texttt{countryPersistence} bean, opens a new connection, and reads data
from the data source. This is the technique used by the sample
application to write the data to Liferay DXP's log.
The second way of accessing data from the external database is to read
data using the helper methods that Service Builder generates in your
application's persistence layer. This technique is demonstrated by the
\texttt{UseJDBC.getCountries} method which first obtains an instance of
the \texttt{CountryLocalService} OSGi service and then invokes
\texttt{countryLocalService.getCountries}. The
\texttt{countryLocalService.getCountries} and
\texttt{countryLocalService.getCountriesCount} methods are two examples
of the persistence layer helper methods that Service Builder generates.
This is the technique used by the sample application to actually display
the data. The portlet's \texttt{view.jsp} uses the
\texttt{\textless{}search-container\textgreater{}} JSP tag to display a
list of results. The results are obtained by the
\texttt{UseJDBC.getCountries} method mentioned above.
\chapter{Service Builder Application Using External Database via
JNDI}\label{service-builder-application-using-external-database-via-jndi}
The \texttt{apps/service-builder/jndi} sample demonstrates how to
connect a Liferay Service Builder application to an external database
via a JNDI connection configured on the application server. Here, an
external database means any database other than Liferay DXP's database.
For this sample to work correctly, you must prepare such an external
database and configure your application server to use it.
\noindent\hrulefill
\textbf{Important:} Connecting to an external data source using JNDI is
broken in Portal CE 7.2 GA1 and GA2, and in DXP 7.2 releases prior to
FP5/SP2. See
\href{https://issues.liferay.com/browse/LPS-107733}{LPS-107733} for
details.
\noindent\hrulefill
Follow the steps below to make the required preparations before
deploying the application.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create an external database based on sample application's
\texttt{service.xml}.
\texttt{service.xml}:
\begin{verbatim}
REGION
\end{verbatim}
The entity's data source name \texttt{extDataSource} is arbitrary but
must be specified in the data source configuration in the application
server (next step).
Here are MariaDB commands to create the database:
\begin{verbatim}
create database external character set utf8;
use external;
create table region(id bigint not null primary key, name varchar(255));
insert into region(id, name) values(1, 'Tasmania');
\end{verbatim}
The database name is arbitrary; the data source configuration in your
application server (next step), however, must specify this same
database. The database table called \texttt{region} represents the
service entity. The table has a \texttt{BIGINT} column called
\texttt{Id} and a \texttt{VARCHAR(255)} column called \texttt{Name}.
Add at least one record to this table. Running
\texttt{select\ *\ from\ region;} should return the record you added.
\item
In your application server configuration, define a JNDI connection to
your database and map it to the \texttt{data-source} name (i.e.,
\texttt{extDataSource}) that the sample \texttt{service.xml} entities
specify.
For example, if Tomcat is your application server, open your
\texttt{{[}LIFERAY\_HOME{]}/tomcat-version/conf/server.xml} file and
add a \texttt{Resource} element like this one inside of the
\texttt{\textless{}GlobalNamingResources\textgreater{}} element:
\begin{verbatim}
\end{verbatim}
Replace the user name and password values and see the
\href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database
Templates} for the URL parameters to use for your database.
\item
If you are using Tomcat, open your
\texttt{{[}LIFERAY\_HOME{]}/tomcat-version/conf/context.xml} file and
add this resource link element inside of the
\texttt{\textless{}Context\textgreater{}} element:
\begin{verbatim}
\end{verbatim}
Now your data source is defined at Tomcat's scope.
\item
Create a
\texttt{com.liferay.blade.samples.jndiservicebuilder.service-log4j-ext.xml}
in your Liferay DXP instance's \texttt{{[}LIFERAY\_HOME{]}/osgi/log4}
folder. Create this folder if it doesn't yet exist. Add this content
to the XML file that you created:
\begin{verbatim}
\end{verbatim}
This XML file defines the log level for the classes in the
\texttt{com.liferay.blade.samples.jndiservicebuilder.service.impl}
package. The
\texttt{com.liferay.blade.samples.jndiservicebuilder.service.impl.RegionLocalServiceImpl}
is the class that will produce log messages when the sample portlet is
viewed.
\end{enumerate}
Now your sample is ready for deployment! Make sure to build and deploy
each of the three modules that comprise the sample application:
\begin{itemize}
\tightlist
\item
\texttt{jndi-api}
\item
\texttt{jndi-service}
\item
\texttt{jndi-web}
\end{itemize}
After these modules have been deployed, add the \texttt{jndi-web}
portlet to a Liferay DXP page.
\begin{figure}
\centering
\includegraphics{./images/jndi-sb-sample.png}
\caption{This sample prints out the values previously inputted into the
database.}
\end{figure}
A sample table is printed in the portlet's view, representing the info
inputted into the database.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-2}
This sample demonstrates two ways to access data from an external
database defined by a JNDI connection:
\begin{itemize}
\tightlist
\item
extract data directly from the raw data source by explicitly
specifying a SQL query.
\item
read data using the helper methods that Service Builder generates in
your application's persistence layer.
\end{itemize}
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-2}
Once you've added the \texttt{jndi-web} portlet to a page, the
\texttt{RegionLocalServiceUtil.useJNDI} method is invoked. This method
accesses the database defined by the JNDI connection you specified and
logs information about the rows in the \texttt{region} table to Liferay
DXP's log.
The first way of accessing data from the external database is to extract
data directly from the raw data source by explicitly specifying a SQL
query. This technique is demonstrated by the
\texttt{RegionLocalServiceImpl.useJNDI} method. That method obtains the
Spring-defined data source that's injected into the
\texttt{regionPersistence} bean, opens a new connection, and reads data
from the data source. This is the technique used by the sample
application to write the data to Liferay DXP's log.
The second way of accessing data from the external database is to read
data using the helper methods that Service Builder generates in your
application's persistence layer. This technique is demonstrated by the
\texttt{UseJNDI.getRegions} method which first obtains an instance of
the \texttt{RegionLocalService} OSGi service and then invokes
\texttt{regionLocalService.getRegions}. The
\texttt{regionLocalService.getRegions} and
\texttt{regionLocalService.getRegionsCount} methods are two examples of
the persistence layer helper methods that Service Builder generates.
This is the technique used by the sample application to actually display
the data. The portlet's \texttt{view.jsp} uses the
\texttt{\textless{}search-container\textgreater{}} JSP tag to display a
list of results. The results are obtained by the
\texttt{UseJNDI.getRegions} method mentioned above.
\section{Additional Information}\label{additional-information}
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/connecting-service-builder-to-an-external-database}{Connecting
to an External Data Source}
\end{itemize}
\chapter{Workflow Samples}\label{workflow-samples}
This section focuses on Liferay's Workflow Framework sample projects
built with various build tools. You can view these samples by visiting
the \texttt{apps/workflow} folder corresponding to your preferred build
tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/workflow}{Gradle
Workflow sample apps}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/apps/workflow}{Liferay
Workspace Workflow sample apps}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/apps/workflow}{Maven
Workflow sample apps}
\end{itemize}
The following Workflow samples are documented:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/workflow-application}{Workflow
application}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/workflow-application-with-asset-integration}{Workflow
application with Asset Integration}
\end{itemize}
Visit a particular sample page to learn more!
\chapter{Workflow Asset Application}\label{workflow-asset-application}
This sample demonstrates workflow enabling a model entity that is an
asset.
To see the Workflow sample in action, complete the following steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the sample widget to a page by navigating to \emph{Add}
(\includegraphics{./images/icon-add.png}) → \emph{Widgets} →
\emph{Sample} → \emph{Workflow Asset} and dragging it to the page.
\item
Go to \emph{Control Panel} → \emph{Workflow} → \emph{Process Builder}
→ \emph{Configuration} and assign a workflow to the Qux entity.
\item
Select the app's \emph{Add} button and add an entity. Do this several
times to create multiple entities.
\item
Go to \emph{User} → \emph{My Workflow Tasks} → \emph{Assigned to My
Roles} and assigned the task to me and Approve the Task.
\end{enumerate}
Now you've taken the entity and successfully run it through a workflow.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-3}
This sample demonstrates Liferay DXP's Workflow Handler API.
Specifically, it demonstrates how to create a \texttt{WorkflowHandler}
for your custom entity that is
\href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{asset
enabled}.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-3}
The basic implementation of \texttt{WorkflowHandler} is done via
extension of the \texttt{BaseWorkflowHandler} class. This is where the
sample leverages the basic methods required for the entity's
\texttt{WorkflowHandler}.
\begin{verbatim}
@Override
public String getClassName() {
return Qux.class.getName();
}
@Override
public String getTitle(long classPK, Locale locale) {
return String.valueOf(classPK);
}
@Override
public String getType(Locale locale) {
return ResourceActionsUtil.getModelResource(locale, getClassName());
}
@Override
public Qux updateStatus(
int status, Map workflowContext)
throws PortalException {
long userId = GetterUtil.getLong(
(String)workflowContext.get(WorkflowConstants.CONTEXT_USER_ID));
long classPK = GetterUtil.getLong(
(String)workflowContext.get(
WorkflowConstants.CONTEXT_ENTRY_CLASS_PK));
return _quxLocalService.updateStatus(userId, classPK, status);
}
\end{verbatim}
For more information on the workflow framework, visit its dedicated
\href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{documentation}.
\chapter{Workflow Application}\label{workflow-application}
The
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/workflow/basic}{\texttt{basic}}
sample demonstrates workflow enabling an entity that is not an asset.
To see the Workflow sample in action, complete the following steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the sample widget to a page by navigating to \emph{Add}
(\includegraphics{./images/icon-add.png}) → \emph{Widgets} →
\emph{Sample} → \emph{Workflow Basic} and dragging it to the page.
\item
Go to \emph{Control Panel} → \emph{Workflow} → \emph{Process Builder}
→ \emph{Configuration} and assign a workflow to the \texttt{Baz}
entity.
\item
Select the app's \emph{Add} button and add an entity. Do this several
times to create multiple entities.
\item
Go to \emph{User} → \emph{My Workflow Tasks} → \emph{Assigned to My
Roles} and assigned the task to me and Approve the Task.
\end{enumerate}
Now you've taken the entity and successfully run it through a workflow.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-4}
This sample demonstrates Liferay DXP's Workflow Handler API.
Specifically, it demonstrates how to create a \texttt{WorkflowHandler}
for your custom entity.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-4}
The basic implementation of \texttt{WorkflowHandler} is done via
extension of the \texttt{BaseWorkflowHandler} class. This is where the
sample leverages the basic methods required for the entity's
\texttt{WorkflowHandler}.
\begin{verbatim}
@Override
public String getClassName() {
return Baz.class.getName();
}
@Override
public String getTitle(long classPK, Locale locale) {
return String.valueOf(classPK);
}
@Override
public String getType(Locale locale) {
return ResourceActionsUtil.getModelResource(locale, getClassName());
}
@Override
public Baz updateStatus(
int status, Map workflowContext)
throws PortalException {
long userId = GetterUtil.getLong(
(String)workflowContext.get(WorkflowConstants.CONTEXT_USER_ID));
long classPK = GetterUtil.getLong(
(String)workflowContext.get(
WorkflowConstants.CONTEXT_ENTRY_CLASS_PK));
return _bazLocalService.updateStatus(userId, classPK, status);
}
\end{verbatim}
For more information on the workflow framework, visit its dedicated
\href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{documentation}.
\chapter{Greedy Policy Option
Application}\label{greedy-policy-option-application}
The Greedy Policy Option sample provides two portlets that can be added
to a Liferay DXP page: Greedy Portlet and Reluctant Portlet.
\begin{figure}
\centering
\includegraphics{./images/greedy-policy-app.png}
\caption{The Greedy Policy Option app provides two portlets that only
print text. You'll dive deeper later to discover their interesting
capabilities involving services.}
\end{figure}
These two portlets do not provide anything useful out-of-the-box. They
are, however, very effective at demonstrating the ability to reference
services using greedy and reluctant policy options. You'll learn how to
do this later.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-5}
This sample provides two modules referencing services using greedy and
reluctant policy options.
\begin{itemize}
\item
\texttt{service-reference}: Provides an OSGi service interface called
\texttt{SomeService}, a default implementation of it, and portlets
that refer to service instances. One portlet refers to new higher
ranked instances of the service automatically. The other portlet is
reluctant to use new higher ranked instances and continues to use its
bound service. The reluctant portlet can, however, be configured
dynamically to use other service instances.
\item
\texttt{higher-ranked-service}: Has a higher ranked
\texttt{SomeService} implementation.
\end{itemize}
Here are each module's file structures:
\begin{itemize}
\tightlist
\item
\texttt{service-reference/}
\begin{itemize}
\tightlist
\item
\texttt{bnd.bnd}
\item
\texttt{configs/}
\begin{itemize}
\tightlist
\item
\texttt{com.liferay.blade.reluctant.vs.greedy.portlet.portlet.ReluctantPortlet.config}
→ \texttt{ReluctantPortlet} configuration file
\end{itemize}
\item
\texttt{src/main/java/com/liferay/blade/reluctant/vs/greedy/portlet/}
\begin{itemize}
\tightlist
\item
\texttt{api/}
\begin{itemize}
\tightlist
\item
\texttt{SomeService.java} → Service interface
\end{itemize}
\item
\texttt{constants/}
\begin{itemize}
\tightlist
\item
\texttt{ReluctantPortletVsGreedyPortletKeys.java} → Portlet
constants
\end{itemize}
\item
\texttt{portlet/}
\begin{itemize}
\tightlist
\item
\texttt{DefaultSomeService.java} → Zero ranked service
implementation
\item
\texttt{GreedyPortlet.java} → Refers to \texttt{SomeService}
using a greedy service policy option
\item
\texttt{ReluctantPortletPortlet.java} → Refers to
\texttt{SomeService} using a reluctant service policy option by
default.
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{higher-ranked-service/}
\begin{itemize}
\tightlist
\item
\texttt{bnd.bnd}
\item
\texttt{src/main/java/com/liferay/blade/reluctant/vs/greedy/svc/HigherRankedService.java}
→ Service implementation with service ranking value of \texttt{100}
\end{itemize}
\end{itemize}
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-5}
Here are the things you can learn using the sample modules:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\hyperref[binding-a-newly-deployed-components-service-reference-to-the-highest-ranking-service-instance-thats-available-initially]{Binding
a component's service reference to the highest ranked service instance
that's available initially.}
\item
\hyperref[deploying-a-module-with-a-higher-ranked-service-instance-for-binding-to-greedy-references-immediately]{Deploying
a module with a higher ranked service instance for binding to greedy
references immediately.}
\item
\hyperref[configuring-a-component-to-reference-a-different-service-instance-dynamically]{Configuring
a component to reference a different service instance dynamically.}
\end{enumerate}
Let's walk through the demonstration.
\section{Binding a newly deployed component's service reference to the
highest ranking service instance that's available
initially}\label{binding-a-newly-deployed-components-service-reference-to-the-highest-ranking-service-instance-thats-available-initially}
On deploying a component that references a service, it binds to the
highest ranking service instance that matches its target filter (if
specified).
The portlet classes refer to instances of interface
\texttt{SomeService}. The \texttt{doSomething} method returns a
\texttt{String}.
\begin{verbatim}
public interface SomeService {
public String doSomething();
}
\end{verbatim}
Class \texttt{DefaultSomeService} implements \texttt{SomeService}. Its
\texttt{doSomething} method returns the \texttt{String} ``I am
Default!''.
\begin{verbatim}
@Component
public class DefaultSomeService implements SomeService {
@Override
public String doSomething() {
return "I am Default!";
}
}
\end{verbatim}
When module's portlets refer to \texttt{DefaultSomeService}, they
display the \texttt{String} ``I am Default!''.
The \texttt{ReluctantPortlet} class's \texttt{SomeService} reference's
policy option is the default: static and reluctant. This policy option
keeps the reference bound to its current service instance unless that
instance stops or the reference is reconfigured to refer to a different
service instance.
\begin{verbatim}
@Component(
immediate = true,
property = {
"com.liferay.portlet.display-category=category.sample",
"com.liferay.portlet.instanceable=true",
"javax.portlet.display-name=Reluctant Portlet",
"javax.portlet.init-param.template-path=/",
"javax.portlet.init-param.view-template=/view.jsp",
"javax.portlet.name=" + ReluctantVsGreedyPortletKeys.Reluctant,
"javax.portlet.resource-bundle=content.Language",
"javax.portlet.security-role-ref=power-user,user"
},
service = Portlet.class
)
public class ReluctantPortlet extends MVCPortlet {
@Override
public void doView(
RenderRequest renderRequest, RenderResponse renderResponse)
throws IOException, PortletException {
renderRequest.setAttribute("doSomething", _someService.doSomething());
super.doView(renderRequest, renderResponse);
}
@Reference
private SomeService _someService;
}
\end{verbatim}
The \texttt{ReluctantPortlet}'s method \texttt{doView} sets render
request attribute \texttt{doSomething} to the value returned from the
\texttt{SomeService} instance's \texttt{doSomething} method (e.g.,
\texttt{DefaultService} returns ``I am default!'').
The \texttt{GreedyPortlet} class is similar to
\texttt{ReluctantPortlet}, except its \texttt{SomeService} reference's
policy option is static and greedy (i.e.,
\texttt{ReferencePolicyOption.GREEDY}).
\begin{verbatim}
public class GreedyPortlet extends MVCPortlet {
@Override
public void doView(
RenderRequest renderRequest, RenderResponse renderResponse)
throws IOException, PortletException {
renderRequest.setAttribute("doSomething", _someService.doSomething());
super.doView(renderRequest, renderResponse);
}
@Reference (policyOption = ReferencePolicyOption.GREEDY)
private SomeService _someService;
}
\end{verbatim}
The greedy policy option lets the component switch to using a higher
ranked \texttt{SomeService} instance if one becomes active in the
system. The section
\hyperref[deploying-a-module-with-a-higher-ranked-service-instance-for-binding-to-greedy-references-immediately]{\emph{Deploying
a module with a higher ranked service instance for binding to greedy
references immediately}} demonstrates this portlet switching to a higher
ranked service.
It's time to see this module's portlets and service in action.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Stop module \texttt{higher-ranked-service} if it's active.
\item
Deploy the \texttt{service-reference} module.
\item
Add the \emph{Reluctant Portlet} from the \emph{Add} → \emph{Widgets}
→ \emph{Sample} category to a site page.
The portlet displays the message ``SomeService says I am
Default!''--whose latter part comes from the render request attribute
set by the \texttt{DefaultService} instance.
\begin{figure}
\centering
\includegraphics{./images/reluctant-portlet-using-default.png}
\caption{\emph{Reluctant Portlet} displays the message ``SomeService
says I am Default!''}
\end{figure}
\item
Add the \emph{Greedy Portlet} from the \emph{Add} → \emph{Widgets} →
\emph{Sample} category to a site page.
The portlet displays the message ``SomeService says I am better, use
me!''. Both portlets are referencing a \texttt{DefaultService}
instance.
\begin{figure}
\centering
\includegraphics{./images/greedy-portlet-using-default.png}
\caption{\emph{Greedy Portlet} displays the message ``SomeService says
I am better, use me!''}
\end{figure}
\end{enumerate}
Since \texttt{DefaultService} is the only active \texttt{SomeService}
instance in the system, the portlets refer to it for their
\texttt{SomeService} fields.
The \texttt{DefaultService} and portlets \emph{Reluctant Portlet} and
\emph{Greedy Portlet} are active. Let's activate a higher ranked
\texttt{SomeService} instance and see how the portlets react to it.
\section{Deploying a module with a higher ranked service instance for
binding to greedy references
immediately}\label{deploying-a-module-with-a-higher-ranked-service-instance-for-binding-to-greedy-references-immediately}
Module \texttt{higher-ranked-service} provides a \texttt{SomeService}
implementation called \texttt{HigherRankedService}.
\texttt{HigherRankedService}'s service ranking is \texttt{100}--that's
\texttt{100} more than \texttt{DefaultService}'s ranking \texttt{0}. Its
\texttt{doSomething} method returns the \texttt{String} ``I am better,
use me!''.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Deploy the \texttt{higher-ranked-service} module.
\item
Refresh your page that has the portlets \emph{Reluctant Portlet} and
\emph{Greedy Portlet}.
\end{enumerate}
\emph{Reluctant Portlet} continues displaying message ``SomeService says
I am better, use me!''. It's ``reluctant'' to unbind from the
\texttt{DefaultService} instance and bind to the newly activated
\texttt{HigherRankedService} service.
\emph{Greedy Portlet} displays a new message ``SomeService says I am
better, use me!''. The part of the message ``I am better, use me!''
comes from the \texttt{HigherRankedService} instance to which it refers.
\begin{figure}
\centering
\includegraphics{./images/greedy-portlet-using-higher-ranked-service.png}
\caption{The \emph{Greedy Portlet} is using a
\texttt{HigherRankedService} instance}
\end{figure}
Next, learn how to bind the \emph{Reluctant Portlet} to a
\texttt{HigherRankedService} instance.
\section{Configuring a component to reference a different service
instance
dynamically}\label{configuring-a-component-to-reference-a-different-service-instance-dynamically}
The \emph{Reluctant Portlet} is currently bound to a
\texttt{DefaultService} instance. It's ``reluctant'' to unbind from it
and bind to a different service. OSGi Configuration Administration lets
you reconfigure service references to filter on and bind to different
service instances.
The \texttt{service-reference} module's configuration files and
\texttt{com.liferay.blade.reluctant.vs.greedy.portlet.portlet.ReluctantPortlet.config}
and
\texttt{com.liferay.blade.reluctant.vs.greedy.portlet.portlet.ReluctantPortlet.cfg}
configure the \texttt{ReluctantPortlet} component to use a
\texttt{HigherRankedService} instance.
\begin{verbatim}
_someService.target=(component.name=com.liferay.blade.reluctant.vs.greedy.service.HigherRankedService)
\end{verbatim}
The service configuration filters on a service whose
\texttt{component.name} is
\texttt{com.liferay.blade.reluctant.vs.greedy.service.HigherRankedService}.
Note: For deploying to 7.0, use file with suffix \texttt{.config}. For
earlier versions (i.e., Liferay DXP 7.0 Fix Packs earlier than Fix Pack
8 and Liferay CE Portal 7.0 GA3 or earlier), use the file with suffix
\texttt{.cfg}.
Here are the steps to reconfigure \texttt{ReluctantPortlet} to use
\texttt{HigherRankedService}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Copy the configuration file to
\texttt{{[}Liferay-Home{]}/osgi/configs}.
\item
Refresh your browser.
\end{enumerate}
\emph{Reluctant Portlet} displays a new message ``SomeService says I am
better, use me!''.
\begin{figure}
\centering
\includegraphics{./images/reluctant-portlet-using-higher-ranked-service.png}
\caption{\emph{Reluctant Portlet} is using the
\texttt{HigherRankedService} instance instead of a
\texttt{DefaultService} instance.}
\end{figure}
\emph{Reluctant Portlet} is using \texttt{HigherRankedService} instance
instead of a \texttt{DefaultService} instance. You've configured
\emph{Reluctant Portlet} to use a \texttt{HigherRankedService} instance!
\section{Where Is This Sample?}\label{where-is-this-sample}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/greedy-policy-option-portlet}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/apps/greedy-policy-option-portlet}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/apps/greedy-policy-option-portlet}{Maven}
\end{itemize}
\chapter{Kotlin Portlet}\label{kotlin-portlet}
The Kotlin Portlet sample provides an input form that accepts a name.
Once submitting a name, the portlet renders a greeting message.
\begin{figure}
\centering
\includegraphics{./images/kotlin-portlet.png}
\caption{After saving the inputted name, it's displayed as a greeting on
the portlet page.}
\end{figure}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-6}
This sample highlights the use of the
\href{https://kotlinlang.org/}{Kotlin} programming language in
conjunction with Liferay's MVC framework. Specifically, this sample
leverages the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCActionCommand.html}{MVCActionCommand}
interface.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-6}
This sample uses the MVC Action Command's \texttt{processAction(...)}
method to process the inputted text (i.e., name). The text is set as an
attribute in the \texttt{KotlinGreeterActionCommandKt.kt} class using an
\texttt{ActionRequest} and then is retrieved in the JSP using a
\texttt{RenderRequest}.
\section{Where Is This Sample?}\label{where-is-this-sample-1}
This sample is built with the following build tools:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/kotlin-portlet}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/apps/kotlin-portlet}{Liferay
Workspace}
\end{itemize}
\chapter{Shared Language Keys}\label{shared-language-keys}
The Shared Language Keys sample provides a JSP portlet that displays
language keys.
\begin{figure}
\centering
\includegraphics{./images/language-web-portlet.png}
\caption{The sample JSP portlet displays three language keys.}
\end{figure}
The language keys displayed in the portlet come from two different
modules.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-7}
This sample is broken into two modules:
\begin{itemize}
\tightlist
\item
\texttt{language}
\item
\texttt{language-web}
\end{itemize}
The \texttt{language-web} module provides a JSP portlet with unique
language keys that it displays. The \texttt{language} module provides a
resource module which only holds language keys. Its sole purpose is to
share language keys with the JSP portlet provided in
\texttt{language-web}. This sample conveys Liferay's recommended
approach to sharing language keys through OSGi services.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-7}
You must deploy both \texttt{language-web} and \texttt{language} modules
to simulate this sample's targeted demonstration.
First, note the language keys provided by each module:
\begin{itemize}
\tightlist
\item
\texttt{language-web}
\begin{itemize}
\tightlist
\item
\texttt{blade\_language\_web\_LanguageWebPortlet.caption=Hello\ from\ BLADE\ Language\ Web!}
\item
\texttt{blade\_language\_web\_override\_LanguageWebPortlet.caption=I\ have\ overridden\ the\ key\ from\ BLADE\ Language\ Module!}
\end{itemize}
\item
\texttt{language}
\begin{itemize}
\tightlist
\item
\texttt{blade\_language\_LanguageWebPortlet.caption=Hello\ from\ the\ BLADE\ Language\ Module!}
\item
\texttt{blade\_language\_web\_override\_LanguageWebPortlet.caption=Hello\ from\ the\ BLADE\ Language\ Module\ but\ you\ won\textquotesingle{}t\ see\ me!}
\end{itemize}
\end{itemize}
When you place the sample BLADE Language Web portlet on a Liferay DXP
page, you're presented with three language keys:
\begin{figure}
\centering
\includegraphics{./images/shared-language-keys.png}
\caption{The Language Web portlet displays three phrases, two of which
are shared from a different module.}
\end{figure}
The first message is provided by the \texttt{language-web} module. The
second message is from the \texttt{language} module. The third message
is provided by both modules; as you can see, the \texttt{language-web}'s
message is used, overriding the \texttt{language} module's identically
named language key.
This sample shows what takes precedence when displaying language keys.
The order for this example goes
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
\texttt{language-web} module language keys
\item
\texttt{language} module language keys
\item
Liferay DXP language keys
\end{enumerate}
So how does sharing language keys work?
By default, the \texttt{ResourceBundleLoaderAnalyzerPlugin} expands
modules with \texttt{/content/Language.properties} files to add provided
capabilities:
\begin{itemize}
\tightlist
\item
\texttt{bundle.symbolic.name}
\item
\texttt{resource.bundle.base.name}
\end{itemize}
Then the deployed \texttt{LanguageExtender} scans modules with those
capabilities to automatically register an associated
\texttt{ResourceBundleLoader}.
You can leverage this functionality to use keys from common language
modules by republishing an aggregate \texttt{ResourceBundleLoader}. This
can be done two ways:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Via Components
You can get a reference to the registered service in your components
as detailed in the
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys}{Overriding
a Module's Language Keys} tutorial. The main disadvantage of this
approach is that it forces you to provide a specific implementation of
the \texttt{ResourceBundleLoader}, making it harder to modularize in
the future.
\item
Via Provide Capability
The same \texttt{LanguageExtender} that registers the services
supports an extended syntax that lets you register an aggregate of a
collection of bundles:
\begin{verbatim}
-liferay-aggregate-resource-bundles: \
blade.language
\end{verbatim}
This approach has the advantage of easier extensibility. When language
keys change, only the common language modules must be built and
redeployed for the modules referencing them to recognize their
updates.
\end{enumerate}
For more information on sharing language keys, visit the
\href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Internationalization}
tutorials.
\section{Where Is This Sample?}\label{where-is-this-sample-2}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/shared-language-keys}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/apps/shared-language-keys}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/apps/shared-language-keys}{Maven}
\end{itemize}
\chapter{Simulation Panel App}\label{simulation-panel-app}
The Simulation Panel App provides new functionality in Liferay DXP's
Simulation Menu. When deploying this sample with no customizations, the
\emph{Simulation Sample} feature is provided in the Simulation Menu with
four options.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-8}
This sample leverages the
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/application/list/PanelApp.html}{PanelApp}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-8}
This sample leverages the \texttt{PanelApp} interface as an OSGi service
via the \texttt{@Component} annotation:
\begin{verbatim}
@Component(
immediate = true,
property = {
"panel.app.order:Integer=500",
"panel.category.key=" + SimulationPanelCategory.SIMULATION
},
service = PanelApp.class
)
\end{verbatim}
There are also two properties provided via the \texttt{@Component}
annotation:
\begin{itemize}
\tightlist
\item
\texttt{panel.app.order}: the order in which the panel app is
displayed among other panel apps in the chosen category. Entries are
ordered from top to bottom. For example, an entry with order
\texttt{1} will be listed above an entry with order \texttt{2}. If the
order is not specified, it's chosen at random based on which service
was registered first in the OSGi container.
\item
\texttt{panel.category.key}: the host panel category for your panel
app, which should be the Simulation Menu category.
\end{itemize}
The simulation panel app extends the
\href{https://docs.liferay.com/ce/apps/web-experience/latest/javadocs/com/liferay/application/list/BaseJSPPanelApp.html}{BaseJSPPanelApp},
which provides a skeletal implementation of the
\href{https://docs.liferay.com/ce/apps/web-experience/latest/javadocs/com/liferay/application/list/PanelApp.html}{PanelApp}
interface with JSP support. JSPs, however, are not the only way to
provide frontend functionality to your panel categories/apps. You can
create your own class implementing \texttt{PanelApp} to use other
technologies, such as FreeMarker.
\section{Where Is This Sample?}\label{where-is-this-sample-3}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/apps/simulation-panel-app}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/apps/simulation-panel-app}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/apps/simulation-panel-app}{Maven}
\end{itemize}
\chapter{Extensions}\label{extensions}
This section focuses on Liferay sample extensions. You can view these
sample extensions by visiting the \texttt{extensions} folder
corresponding to your preferred build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions}{Gradle
sample extensions}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions}{Liferay
Workspace sample extensions}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions}{Maven
sample extensions}
\end{itemize}
Visit a particular sample page to learn more!
\chapter{Control Menu Entry}\label{control-menu-entry}
The Control Menu Entry sample provides a customizable button that is
added to Liferay Portal's default Control Menu. When deploying this
sample with no customizations, an additional button is added to the User
(right side) portion of the Control Menu.
\begin{figure}
\centering
\includegraphics{./images/controlmenuentry.png}
\caption{The User area of the Control Menu is provided an additional
link button when the Control Menu Entry sample is deployed to Liferay
DXP.}
\end{figure}
The button navigates the user to Liferay's website:
https://www.liferay.com.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-9}
This sample leverages the
\href{https://docs.liferay.com/dxp/apps/web-experience/latest/javadocs/com/liferay/product/navigation/control/menu/ProductNavigationControlMenuEntry.html}{ProductNavigationControlMenuEntry}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-9}
This sample first leverages the
\texttt{ProductNavigationControlMenuEntry} interface as an OSGi service
via the \texttt{@Component} annotation:
\begin{verbatim}
@Component(
immediate = true,
property = {
"product.navigation.control.menu.category.key=" + ProductNavigationControlMenuCategoryKeys.USER,
"product.navigation.control.menu.entry.order:Integer=1"
},
service = ProductNavigationControlMenuEntry.class
)
\end{verbatim}
There are also two properties provided via the \texttt{@Component}
annotation:
\begin{itemize}
\tightlist
\item
\texttt{product.navigation.control.menu.category.key}: the category in
which your entry should reside. The default Control Menu provides
three categories: \emph{SITES} (left portion), \emph{TOOLS} (middle
portion), and \emph{USER} (right portion).
\item
\texttt{product.navigation.control.menu.entry.order:Integer}: the
order in which your entry will be displayed in the category. Entries
are ordered from left to right. For example, an entry with order
\texttt{1} will be listed to the left of an entry with order
\texttt{2}. If the order is not specified, it's chosen at random based
on which service was registered first in the OSGi container.
\end{itemize}
This sample also implements the
\texttt{ProductNavigationControlMenuEntry} interface. The following
methods are implemented:
\begin{itemize}
\tightlist
\item
\texttt{getIcon(HttpServletRequest)}
\item
\texttt{getLabel(Locale)}
\item
\texttt{getURL(HttpServletRequest)}
\item
\texttt{isShow(HttpServletRequest)}
\end{itemize}
Refer to this sample's \texttt{BladeProductNavigationControlMenuEntry}
class for Javadocs describing these methods.
\section{Where Is This Sample?}\label{where-is-this-sample-4}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions/control-menu-entry}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions/control-menu-entry}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions/control-menu-entry}{Maven}
\end{itemize}
\chapter{Document Action}\label{document-action}
The Document Action sample shows how to add a context menu option to an
entry in the Documents and Media portlet. When deploying this sample
with no customizations, an additional menu option is available in the
Documents and Media Admin portlet and the Documents and Media portlet.
This sample creates a \emph{Blade Basic Info} option that displays basic
information about the entry (e.g., file name, type, version, etc.). For
example, the Admin portlet provides the new option as illustrated in the
images below:
\begin{figure}
\centering
\includegraphics{./images/documents-and-media-admin-portlet.png}
\caption{The new \emph{Blade Basic Info} option is available from the
entry's Options menu.}
\end{figure}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-10}
This sample leverages the
\href{@product-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/configuration/icon/PortletConfigurationIcon.html}{PortletConfigurationIcon}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-10}
There are four Java classes used in this sample:
\begin{itemize}
\tightlist
\item
\texttt{BladeActionConfigurationIcon}: Adds the new context menu
option to the Document Detail screen options
(\includegraphics{./images/icon-options.png}) (top right corner) of
the Documents and Media Admin portlet. See the
\href{/docs/7-0/tutorials/-/knowledge_base/t/configuring-your-admin-apps-actions-menu}{Configuring
Your Admin App's Actions Menu} tutorial for more details.
\item
\texttt{BladeActionDisplayContext}: Adds the Display Context for the
document action. More about Display Contexts are described later.
\item
\texttt{BladeActionDisplayContextFactory}: Adds the Display Context
factory for the document action.
\item
\texttt{BladeDocumentActionPortlet}: Provides the portlet class, which
extends the
\href{https://portals.apache.org/pluto/portlet-2.0-apidocs/javax/portlet/GenericPortlet.html}{GenericPortlet}.
This class generates what is shown when the context menu option is
selected.
\end{itemize}
A Display Context is a Java class that controls access to a portlet
screen's UI elements. For example, the Document Library would use
Display Contexts to provide its screens all their UI elements. It would
use one Display Context for its document edit screen, another for its
document view screen, etc. A portlet ideally uses a different Display
Context for each of its screens.
A screen's JSP calls on the Display Context (DC) to get elements to
render and to decide whether to render certain types of elements. Some
of the DC methods return a collection of UI elements (e.g., a menu
object of menu items), while other DC methods return booleans that
determine whether to show particular element types. The DC decides which
objects to display, while the JSP organizes the rendered objects and
implements the screen's look and feel. You don't have to decide which
elements to display in your JSP; simply call the DC methods to populate
UI components with objects to render.
To customize or extend a portlet screen that uses a DC, you can extend
the DC and override the methods that control access to the elements that
interest you. For example, you can turn off displaying certain types of
elements (e.g., actions) by overriding the DC method that makes that
decision. You can add new custom elements (e.g., new actions) or remove
existing elements (e.g., a delete action) from a collection of elements
a DC method returns. The beauty of customizing via a DC is that you
don't have to modify the JSP. You only modify the particular methods
that are related to the UI customization goals. And JSP updates won't
break the DC customizations. Replacing a JSP, on the other hand, can
lead to missing an important JSP modification that a new Liferay version
introduces.
As you create custom portlets, you may want to implement DCs. You can
benefit from the separation of concerns that DCs provide and customers
can extend your portlet DCs to specify which UI elements to display. And
they don't need to worry about missing out on the updates you make to
the JSPs.
\section{Where Is This Sample?}\label{where-is-this-sample-5}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions/document-action}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions/document-action}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions/document-action}{Maven}
\end{itemize}
\chapter{Gogo Shell Command}\label{gogo-shell-command}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
The Gogo Shell Command sample demonstrates adding a custom command to
Liferay DXP's Gogo shell environment. All Liferay DXP installations have
a Gogo shell environment, which lets system administrators interact with
Liferay DXP's module framework on a local server machine.
This example adds a new custom Gogo shell command called
\texttt{usercount} under the \texttt{blade} scope. It prints out the
number of registered users on your Liferay DXP installation.
To test this sample, follow the instructions below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Start a Liferay DXP installation.
\item
Navigate to the Control Panel → \emph{Configuration} → \emph{Gogo
Shell}.
\item
Execute \texttt{help} to view all the available commands. The sample
Gogo shell command is listed.
\begin{figure}
\centering
\includegraphics{./images/gogo-shell-1.png}
\caption{The sample Gogo shell command is listed with all the
available commands.}
\end{figure}
\item
Execute \texttt{usercount} to execute the new custom command. The
number of users on your running Liferay Portal installation is
printed.
\begin{figure}
\centering
\includegraphics{./images/gogo-shell-2.png}
\caption{The outcome of executing the \texttt{usercount} command.}
\end{figure}
\end{enumerate}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-11}
This sample demonstrates creating a new Gogo shell command by leveraging
\texttt{osgi.command.*} properties in a Java class.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-11}
To add this new Gogo shell command, you must implement the logic in a
Java class with the following two properties:
\begin{itemize}
\tightlist
\item
\texttt{osgi.command.function}: the command's name, which must match
the method name in the registered service implementation.
\item
\texttt{osgi.command.scope}: the general scope or namespace for the
command.
\end{itemize}
These properties are set in your class's \texttt{@Component} annotation
like this:
\begin{verbatim}
@Component(
property = {"osgi.command.function=usercount", "osgi.command.scope=blade"},
service = Object.class
)
\end{verbatim}
The logic for the \texttt{usercount} command is specified in the method
with the same name:
\begin{verbatim}
public void usercount() {
System.out.println(
"# of users: " + getUserLocalService().getUsersCount());
}
\end{verbatim}
This method uses \emph{Declarative Services} to get a reference for the
\texttt{UserLocalService} to invoke the \texttt{getUsersCount} method.
This lets you find the number of users currently in the system.
For more information on using the Gogo shell, see the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Using
the Felix Gogo Shell} tutorial.
\section{Where Is This Sample?}\label{where-is-this-sample-6}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions/gogo}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions/gogo}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions/gogo}{Maven}
\end{itemize}
\chapter{Index Settings Contributor}\label{index-settings-contributor}
The Index Settings Contributor sample demonstrates how to add a custom
type mapping to Liferay DXP. You can demo this sample by completing the
following steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the \emph{Control Panel} → \emph{Configuration} →
\emph{Search} menu.
\item
Click \emph{Execute} for the \emph{Reindex all search indexes} action.
All properties defined in your \texttt{.json} file are added to
Liferay DXP's search engine. This sample adds the following index
properties:
\begin{itemize}
\tightlist
\item
\texttt{sampleDate}
\item
\texttt{sampleDouble}
\item
\texttt{sampleLong}
\item
\texttt{sampleText}
\end{itemize}
You'll verify this next.
\item
Find your Liferay DXP's instance ID. This can be found in the
\emph{Control Panel} → \emph{Configuration} → \emph{Virtual Instances}
menu.
\item
Navigate to the following URL:
\begin{verbatim}
http://localhost:9200/liferay-[INSTANCE_ID]/_mapping/LiferayDocumentType?pretty
\end{verbatim}
Be sure to insert your instance ID into the URL.
\item
Verify the added properties are listed.
\begin{figure}
\centering
\includegraphics{./images/index-settings-contributor.png}
\caption{This sample added four new index properties.}
\end{figure}
\end{enumerate}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-12}
This sample leverages the
\href{https://docs.liferay.com/dxp/apps/foundation/latest/javadocs/com/liferay/portal/search/elasticsearch/settings/IndexSettingsContributor.html}{IndexSettingsContributor}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-12}
Liferay's search engine provides an API to define custom mappings. To
use it, follow these fundamental steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Define the new mapping. In this sample, the mapping is defined in the
\texttt{META-INF/mappings/resources/index-type-mappings.json} file.
Notice that the default document for Liferay DXP is called
\texttt{LiferayDocumentType}. The mapping's features can be found in
\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/mapping.html}{Elasticsearch's
docs}.
\item
Inject the mapping into Elasticsearch. The
\texttt{IndexSettingsContributor} class' components are invoked during
the reindexing stage and receive a \texttt{TypeMappingsHelper} as a
hook to add new mappings.
\end{enumerate}
This sample has two classes:
\begin{itemize}
\item
\texttt{ResourceUtil}: reads the \texttt{.json} file.
\item
\texttt{IndexSettingsContributor}: allows the addition of type
mappings on Liferay DXP's search engine.
\end{itemize}
The \texttt{IndexSettingsContributor}'s \texttt{contribute} method adds
the type mappings:
\begin{verbatim}
@Override
public void contribute(
String indexName, TypeMappingsHelper typeMappingsHelper) {
try {
String mappings = ResourceUtil.readResouceAsString(
"META-INF/resources/mappings/index-type-mappings.json");
typeMappingsHelper.addTypeMappings(indexName, mappings);
}
catch (Exception e) {
e.printStackTrace();
}
}
\end{verbatim}
For the \texttt{ResourceUtil.readResouceAsString} parameter, you should
pass the path for the \texttt{.json} file that contains the properties
to be added.
Also, it is important to highlight the
\texttt{IndexSettingsContributor}'s \texttt{@Component} annotation that
registers a new service to the OSGi container:
\begin{verbatim}
@Component(
immediate = true,
service = com.liferay.portal.search.elasticsearch6.settings.IndexSettingsContributor.class
)
> If using Elasticsearch 7, the value of the `service` property is instead `com.liferay.portal.search.elasticsearch7.settings.IndexSettingsContributor.class`.
\end{verbatim}
This sample demonstrates the essentials needed to contribute your own
index settings.
\section{Where Is This Sample?}\label{where-is-this-sample-7}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/blob/7.2/gradle/extensions/index-settings-contributor}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/blob/7.2/liferay-workspace/extensions/index-settings-contributor}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/blob/7.2/maven/extensions/index-settings-contributor}{Maven}
\end{itemize}
\chapter{Indexer Post Processor}\label{indexer-post-processor}
The Indexer Post Processor sample demonstrates using the
\texttt{IndexerPostProcessor} interface, which is provided to customize
search queries and documents before they're sent to the search engine,
and/or customize result summaries when they're returned to end users.
This basic demonstration prints a message in the log when one of the
\texttt{*IndexerPostProcessor} methods is called.
To see this sample's messages in Liferay DXP's log, you must add a
logging category to the portal. Navigate to \emph{Control Panel} →
\emph{Configuration} → \emph{Server Administration} and click on
\emph{Log Levels} → \emph{Add Category}. Then fill out the form:
\begin{itemize}
\tightlist
\item
\emph{Logger Name}:
\texttt{com.liferay.blade.samples.indexerpostprocessor}
\item
\emph{Log Level}: \texttt{INFO}
\end{itemize}
Once you save the new logging category, you can witness the sample
indexer post processor in action. For example, you can test the sample's
\texttt{BlogsIndexerPostProcessor} implementation by creating a blog
entry. When you publish the blog, the following message is logged in the
console:
\begin{verbatim}
18:27:30,737 INFO [http-nio-8080-exec-8][BlogsIndexerPostProcessor:76] postProcessDocument
\end{verbatim}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-13}
This sample leverages the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/search/IndexerPostProcessor.html}{IndexerPostProcessor}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-13}
This sample contains four implementations of the
\texttt{IndexerPostProcessor} interface:
\begin{itemize}
\tightlist
\item
\texttt{BlogsIndexerPostProcessor}
\item
\texttt{MultipleEntityIndexerPostProcessor}
\item
\texttt{MultipleIndexerPostProcessor}
\item
\texttt{UserEntityIndexerPostProcessor}
\end{itemize}
All these classes leverage the interface as an OSGi service via the
\texttt{@Component} annotation. For example, the \texttt{@Component}
annotation of the \texttt{UserEntityIndexerPostProcessor} looks like
this:
\begin{verbatim}
@Component(
immediate = true,
property = {
"indexer.class.name=com.liferay.portal.kernel.model.User",
"indexer.class.name=com.liferay.portal.kernel.model.UserGroup"
},
service = IndexerPostProcessor.class
)
\end{verbatim}
There's one property type provided via the \texttt{@Component}
annotation:
\begin{itemize}
\tightlist
\item
\texttt{indexer.class.name}: the fully qualified class name of the
indexed entity or an \texttt{Indexer} class itself.
\end{itemize}
This sample's implementations of the \texttt{IndexerPostProcessor}
interface override the following methods:
\begin{itemize}
\tightlist
\item
\texttt{postProcessContextBooleanFilter}
\item
\texttt{postProcessContextQuery}
\item
\texttt{postProcessDocument}
\item
\texttt{postProcessFullQuery}
\item
\texttt{postProcessSearchQuery(BooleanQuery,\ BooleanFilter)}
\item
\texttt{postProcessSearchQuery(BooleanQuery,\ SearchContext)}
\item
\texttt{postProcessSummary}
\end{itemize}
For more information on Liferay's Search API, refer to the
\href{/docs/7-2/frameworks/-/knowledge_base/f/search}{Introduction to
Liferay Search} article.
\section{Where Is This Sample?}\label{where-is-this-sample-8}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions/indexer-post-processor}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions/indexer-post-processor}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions/indexer-post-processor}{Maven}
\end{itemize}
\chapter{Model Listener}\label{model-listener}
The Model Listener sample demonstrates adding a custom model listener to
a Liferay Portal out-of-the-box entity. When deploying this sample with
no customizations, a custom model listener is added to the portal's
layouts, listening for \texttt{onBeforeCreate} events. This means that
any page creation will trigger this listener, which will execute before
the new page is created.
For example, if a new page is added with the name \emph{My Test Page},
the following message is printed to the console:
\begin{figure}
\centering
\includegraphics{./images/model-listener-1.png}
\caption{The sample model listener's message in the console.}
\end{figure}
You can also verify that the model listener sample was executed by
navigating to the new page's \emph{Options} → \emph{Configure Page} →
\emph{SEO} option. The HTML Title field looks like this:
\begin{figure}
\centering
\includegraphics{./images/model-listener-2.png}
\caption{The page's HTML title updated by the model listener sample.}
\end{figure}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-14}
This sample leverages the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/ModelListener.html}{ModelListener}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-14}
Model Listeners are used to listen for persistence events on models and
take actions as a result of those events. Actions can be executed on an
entity's database table before or after a \texttt{create},
\texttt{remove}, \texttt{update}, \texttt{addAssociation}, or
\texttt{removeAssociation} event. It's possible to have more than one
model listener on a single model too; the execution order is not
guaranteed.
There are two steps to create a new model listener:
\begin{itemize}
\tightlist
\item
Implement a Model Listener class
\item
Register the new service in Liferay's OSGi runtime
\end{itemize}
This sample adds the model listener logic in a new Java class named
\texttt{CustomLayoutListener} that extends
\href{https://docs.liferay.com/dxp/portal/7.1-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/BaseModelListener.html}{BaseModelListener}.
\begin{verbatim}
public class CustomLayoutListener extends BaseModelListener {
@Override
public void onBeforeCreate(Layout model) throws ModelListenerException {
System.out.println(
"About to create layout: " + model.getNameCurrentValue());
model.setTitle("Title generated by model listener!");
}
}
\end{verbatim}
Important things to note in this code snippet are
\begin{itemize}
\tightlist
\item
The entity to be targeted by this model listener is specified as the
parameterized type (e.g., \texttt{Layout}).
\item
The overridden methods dictate the type of event(s) that are listened
for (e.g., \texttt{onBeforeCreate}); they also trigger the logic
execution.
\end{itemize}
The final step is registering the service in Liferay's OSGi runtime,
which is accomplished by the following annotation (if using Declarative
Services):
\begin{verbatim}
@Component(immediate = true, service = ModelListener.class)
\end{verbatim}
For more information on model listeners, see the
\href{/docs/7-2/customization/-/knowledge_base/c/model-listeners}{Creating
Model Listeners} tutorial.
\section{Where Is This Sample?}\label{where-is-this-sample-9}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions/model-listener}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions/model-listener}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions/model-listener}{Maven}
\end{itemize}
\chapter{Screen Name Validator}\label{screen-name-validator}
The Screen Name Validator sample provides a way to validate a user's
inputted screen name. During validation, the screen name is tested
client-side and server-side.
This sample checks if a user's screen name contains reserved words that
are configured in the \emph{Control Panel} → \emph{Configuration} →
\emph{System Settings} → \emph{Foundation} → \emph{ScreenName Validator}
menu. The default values for the screen name validator's reserved words
are \emph{admin} and \emph{user}.
\begin{figure}
\centering
\includegraphics{./images/screenname-validator-config.png}
\caption{Enter reserved words for the screen name validator.}
\end{figure}
You can test this sample by following the following steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Deploy the Screen Name Validator to your portal installation.
\item
Navigate to the \emph{Control Panel} → \emph{Users} → \emph{Users and
Organizations} menu.
\item
Create a new user by selecting the \emph{Add User}
(\includegraphics{./images/icon-add.png}) button.
\item
Adding a screen name that contains the word \emph{admin} or
\emph{user}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/screenname-validator-test.png}
\caption{The error message displays when inputting a reserved word for
the screen name.}
\end{figure}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-15}
This sample leverages the
\href{@product-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/auth/ScreenNameValidator.html}{ScreenNameValidator}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-15}
To customize this sample, modify its
\texttt{com.liferay.blade.samples.screenname.validator.internal.CustomScreenNameValidator}
class.
You can also customize this sample's configuration by adding more
properties in its
\texttt{com.liferay.blade.samples.screenname.validator.CustomScreenNameConfiguration}
class.
For more information on customizing the Validation sample to fit your
needs, see the Javadoc provided in this sample's Java classes.
\section{Where Is This Sample?}\label{where-is-this-sample-10}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions/screen-name-validator}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions/screen-name-validator}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions/screen-name-validator}{Maven}
\end{itemize}
\chapter{Servlet}\label{servlet}
The Servlet sample provides an OSGi Whiteboard Servlet in Liferay DXP.
When deploying this sample and configuring the servlet, a \emph{Hello
World} message is displayed when accessing the servlet page URL. Log
info is also outputted to your console.
\begin{figure}
\centering
\includegraphics{./images/servlet-sample.png}
\caption{The servlet displays \emph{Hello World} from the configured
servlet page URL.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/servlet-sample-log.png}
\caption{The servlet also logs info in the console.}
\end{figure}
To configure the servlet in Liferay DXP, complete the following steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the \emph{Control Panel} → \emph{Configuration} →
\emph{Server Administration} → \emph{Log Levels}.
\item
Select \emph{Add Category}.
\item
Insert \emph{com.liferay.blade.samples.servlet.BladeServlet} for the
Logger Name and \emph{INFO} for the Log Level.
\item
Navigate to the http://localhost:8080/o/blade/servlet URL.
\end{enumerate}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-16}
This sample leverages the
\href{https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/http/HttpServlet.html}{HttpServlet}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-16}
To customize this sample, modify its
\texttt{com.liferay.blade.samples.servlet.BladeServlet} class. This
class extends the \texttt{HttpServlet} class. Creating your own servlet
for Liferay DXP is useful when you need to implement servlet actions.
For example, if you wanted to implement the CMIS server by yourself with
\href{https://chemistry.apache.org/}{Apache Chemistry}, you would need
to implement your own servlet, managing requests at a low level.
\section{Where Is This Sample?}\label{where-is-this-sample-11}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/extensions/servlet}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/extensions/servlet}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/extensions/servlet}{Maven}
\end{itemize}
\chapter{Overrides}\label{overrides}
This section focuses on Liferay sample overrides. You can view these
sample overrides by visiting the \texttt{overrides} folder corresponding
to your preferred build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/overrides}{Gradle
sample overrides}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/overrides}{Liferay
Workspace sample overrides}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/overrides}{Maven
sample overrides}
\end{itemize}
Visit a particular sample page to learn more!
\chapter{Module JSP Override}\label{module-jsp-override}
The Module JSP Override sample conveys how to override an application's
JSP by leveraging OSGi fragment modules. This is not the recommended
practice for overriding JSPs in 7.0. See the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps}{Customizing
JSPs} article for better options.
This example overrides the default \texttt{login.jsp} file in the
\texttt{com.liferay.login.web} bundle by adding the red text
\emph{changed} to the Sign In form.
\begin{figure}
\centering
\includegraphics{./images/hook-jsp.png}
\caption{The customized Sign In form with the new \emph{changed} text.}
\end{figure}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-17}
This sample demonstrates how to create a fragment host module and
configure it to override an existing module's JSP.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-17}
You can create your own JSP override by
\begin{itemize}
\tightlist
\item
Declaring the fragment host.
\item
Providing the JSP that will override the original one.
\end{itemize}
To properly declare the fragment host in the \texttt{bnd.bnd} file, you
must specify the host module's (where the original JSP is located)
Bundle Symbolic Name and the host module's exact version to which the
fragment belongs. In this example, this is configured like this:
\begin{verbatim}
Fragment-Host: com.liferay.login.web;bundle-version="1.0.0"
\end{verbatim}
Then you must provide the new JSP intended to override the original one.
Be sure to mimic the host module's folder structure when overriding its
JAR. For this example, since the original JSP is in the folder
\texttt{/META-INF/resources/login.jsp}, the new JSP file resides in the
folder \texttt{src/main/resources/META-INF/resources/login.jsp}.
If needed, you can also target the original JSP following one of the two
possible naming conventions: \texttt{original} or \texttt{portal}. This
pattern looks like
\begin{verbatim}
\end{verbatim}
or
\begin{verbatim}
\end{verbatim}
This approach can be used to override any application JSP (i.e., JSPs
residing in a module). You can also add new JSPs to an existing module
with this technique. For more information on other ways to customize
JSPs, see the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-jsps}{Customizing
JSPs} articles.
\section{Where Is This Sample?}\label{where-is-this-sample-12}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/overrides/module-jsp-override}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/overrides/module-jsp-override}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/overrides/module-jsp-override}{Maven}
\end{itemize}
\chapter{Resource Bundle Override}\label{resource-bundle-override}
This example overrides the default
\texttt{javax.portlet.title.com\_liferay\_login\_web\_portlet\_LoginPortlet}
language key for Liferay DXP's default Login portlet. After deploying
this sample to Liferay DXP, the Login portlet's \emph{Sign In} title is
modified to display \emph{Login Portlet Override}.
\begin{figure}
\centering
\includegraphics{./images/hook-resourcebundle.png}
\caption{The customized Login portlet displays the new language key.}
\end{figure}
For reference, the Login portlet's language keys are stored in the
\href{https://github.com/liferay/liferay-portal}{liferay-portal} Github
repo's \texttt{modules/apps/login/login-web/src/main/resources/content}
folder.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-18}
This sample leverages the
\href{https://bnd.bndtools.org/chapters/220-contracts.html}{\texttt{Provide-Capability}}
OSGi manifest header.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-18}
This sample conveys the recommended approach to override a portlet's
language keys file for any module that is deployed to Liferay DXP's OSGi
runtime (not applicable to Liferay DXP's core language keys).
The steps to override a portlet's language keys are
\begin{itemize}
\tightlist
\item
Provide the new language keys that will override the original ones.
\item
Prioritize the new module's resource bundle.
\end{itemize}
This sample's \texttt{src/main/resources/content} folder holds the
language properties file to override. Since this example's goal is to
override only the English keys, the \texttt{Language\_en.properties} is
added. You can add more language properties files for additional
language key locales you want to override (e.g.,
\texttt{Language\_en.properties} for Spanish).
Once your language keys are in place, you must use OSGi manifest headers
to specify your custom language keys are for the target module. To
compliment the target module's resource bundle, you must aggregate your
resource bundle with the target module's resource bundle. This is done
by ranking your module first to prioritize its resource bundle over the
target module resource bundle. See this sample's \texttt{bnd.bnd} as an
example for setting the \texttt{Provide-Capability} OSGi header:
\begin{verbatim}
Provide-Capability:\
liferay.resource.bundle;\
resource.bundle.base.name="content.Language",\
liferay.resource.bundle;\
bundle.symbolic.name=com.liferay.login.web;\
resource.bundle.aggregate:String="(bundle.symbolic.name=com.liferay.blade.login.web.resource.bundle.override),(bundle.symbolic.name=com.liferay.login.web)";\
resource.bundle.base.name="content.Language";\
service.ranking:Long="2";\
servlet.context.name=login-web
\end{verbatim}
For more information on the \texttt{Provide-Capability} header and its
parts, see the
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys\#prioritize-your-modules-resource-bundle}{Prioritze
Your Module's Resource Bundle} section.
This approach can be used to override any portlet's language keys (i.e.,
\texttt{language.properties} files that are inside a module deployed to
Liferay DXP's OSGi runtime). If you need to override Liferay DXP's core
language keys, see the
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys}{Overriding
Global Language Keys} article.
For more information on using a resource bundle to override a module's
language keys, see the
\href{/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys}{Overriding
a Module's Language Keys} tutorial.
\section{Where Is This Sample?}\label{where-is-this-sample-13}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/overrides/login-web-resource-bundle-override}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/overrides/login-web-resource-bundle-override}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/overrides/login-web-resource-bundle-override}{Maven}
\end{itemize}
\chapter{Themes}\label{themes}
This section focuses on Liferay sample themes. You can view these sample
themes by visiting the \texttt{themes} folder corresponding to your
preferred build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/themes}{Gradle
sample themes}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/themes}{Liferay
Workspace sample themes}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/themes}{Maven
sample themes}
\end{itemize}
Visit a particular sample page to learn more!
\chapter{Simple Theme}\label{simple-theme}
The Simple Theme sample provides the base files for a theme, using the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-gradle-plugin}{Theme
Builder Gradle plugin}. When deploying this sample with no
customizations, a theme based off of the \texttt{\_styled} base theme is
created.
\begin{figure}
\centering
\includegraphics{./images/theme.png}
\caption{A theme based off of the Styled base theme is created when the
Theme Blade sample is deployed to Liferay Portal.}
\end{figure}
For more information on themes, visit the
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Introduction
to Themes} tutorial.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-19}
This sample demonstrates a way to create a simple theme in Liferay DXP.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-19}
To modify this sample, add the \texttt{images}, \texttt{js}, or
\texttt{templates} folder, along with your modified files, to the
\texttt{src/main/webapp} folder. The sample already provides the
\texttt{src/main/resources/resources-importer},
\texttt{src/main/webapp/WEB-INF}, and \texttt{src/main/webapp/css}
folders for you. Add your style modifications to the provided
\texttt{css/\_custom.scss} file. For a complete explanation of a theme's
files, see the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide}{Theme
Reference Guide}.
\section{Where Is This Sample?}\label{where-is-this-sample-14}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/themes/simple-theme}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/wars/simple-theme}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/themes/simple-theme}{Maven}
\end{itemize}
\chapter{Template Context
Contributor}\label{template-context-contributor}
The Template Context Contributor sample injects a new variable into
Liferay DXP's theme context. When deploying this sample with no
customizations, you can use the \texttt{\$\{sample\_text\}} variable
from any theme.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-20}
Many developers prefer using templating frameworks like FreeMarker and
Velocity, but don't have access to the common objects offered to those
working with JSPs. Context contributors allow non-JSP developers an easy
way to inject variables into their Liferay templates.
This sample leverages the
\href{@product-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/template/TemplateContextContributor.html}{TemplateContextContributor}
API.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-20}
You can easily modify this sample by customizing its
\texttt{BladeTemplateContextContributor.java} Java class. For example,
the default context contributor sample provides the
\texttt{\$\{sample\_text\}} variable by injecting it into Liferay's
\texttt{contextObjects}, which is a map provided by default to offer
common variables to non-JSP template developers. You can easily inject
your own variables into the \texttt{contextObjects} map usable by any
theme deployed to Liferay DXP.
Are you working with templates that aren't themes (e.g., ADTs, DDM
templates, etc.)? You can change the context in which your variables are
injected by modifying the \texttt{property} attribute in the
\texttt{@Component} annotation. If you want your variable available for
all templates, change it to
\begin{verbatim}
property = {"type=" + TemplateContextContributor.TYPE_GLOBAL}
\end{verbatim}
For more information on customizing the Template Context Contributor
sample to fit your needs, see the Javadoc listed in this sample's
\texttt{com.liferay.blade.samples.theme.contributorBladeTemplateContextContributor}
class. For more information on context contributors and how to create
them in Liferay DXP, visit the
\href{/docs/7-2/frameworks/-/knowledge_base/f/injecting-additional-context-variables-and-functionality-into-your-theme-templates}{Context
Contributors} tutorial.
\section{Where Is This Sample?}\label{where-is-this-sample-15}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/themes/template-context-contributor}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/themes/template-context-contributor}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/themes/template-context-contributor}{Maven}
\end{itemize}
\chapter{Theme Contributor}\label{theme-contributor}
The Theme Contributor sample contributes updates to the UI of the theme
body, Control Menu, Product Menu, and Simulation Panel. When deploying
this sample with no customizations, the colors of the theme and
aforementioned menus are updated.
\begin{figure}
\centering
\includegraphics{./images/theme-contributor-yellow.png}
\caption{Your Liferay DXP pages and menu fonts now have a yellow tint.}
\end{figure}
Also, there's a simple JavaScript update that is provided, which logs a
message to the browser's console window that states \emph{Hello Blade
Theme Contributor!}.
\begin{figure}
\centering
\includegraphics{./images/theme-contributor-console-output.png}
\caption{The message is printed to your browser's console window using
JavaScript.}
\end{figure}
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-21}
This sample demonstrates a way to contribute updates to a Liferay DXP
theme. Theme Contributors let you package UI resources (e.g., CSS and
JS) independent of a theme to include on a Liferay DXP page.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-21}
To modify this sample, replace the corresponding JS or SCSS file with
the JavaScript or styles that you want, or add your own JS or SCSS
files. For example, this sample provides an update to the Control Menu's
\texttt{background-color} in its
\texttt{src/main/resources/META-INF/resources/css/blade.theme.contributor/\_control\_menu.scss}
file:
\begin{verbatim}
body {
.control-menu {
background-color: darkkhaki;
}
}
\end{verbatim}
All of the SCSS files used in this sample are imported into the main
\texttt{blade.theme.contributor.scss} file:
\begin{verbatim}
@import "bourbon";
@import "mixins";
@import "blade.theme.contributor/body";
@import "blade.theme.contributor/control_menu";
@import "blade.theme.contributor/product_menu";
@import "blade.theme.contributor/simulation_panel";
\end{verbatim}
If you add your own \texttt{SCSS} files, you must add them to the list
of imports in the \texttt{blade.theme.contributor.scss} file.
Likewise, the sample \texttt{blade.theme.contributor.js} logs a message
to your browser's console window using the following JS logic:
\begin{verbatim}
console.log('Hello Blade Theme Contributor!');
\end{verbatim}
For more information on Theme Contributors, visit the
\href{/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site}{Theme
Contributors} tutorial.
\section{Where Is This Sample?}\label{where-is-this-sample-16}
There are three different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/themes/theme-contributor}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/themes/theme-contributor}{Liferay
Workspace}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/maven/themes/theme-contributor}{Maven}
\end{itemize}
\chapter{Ext}\label{ext}
This section focuses on Liferay Ext modules. You can view these sample
apps by visiting the \texttt{ext} folder corresponding to your preferred
build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/ext}{Gradle
sample apps}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/ext}{Liferay
Workspace sample apps}
\end{itemize}
Visit the sample page to learn more!
\chapter{Login Web Ext}\label{login-web-ext}
The Login Ext Module sample demonstrates how to customize a default
Liferay module's source code. This example replaces the default
\texttt{login.jsp} file in the \texttt{com.liferay.login.web} bundle by
adding the text \emph{Hello from com.liferay.login.web.ext module! 2 + 2
= 4} to the Sign In form.
\begin{figure}
\centering
\includegraphics{./images/login-ext.png}
\caption{The Login Ext module customizes the original Login module.}
\end{figure}
It also prints the following text to the console when you select
\emph{Forgot Password} from the Sign In form:
\begin{verbatim}
In com.liferay.login.web.internal.portlet.action.ForgotPasswordMVCRenderCommand render
\end{verbatim}
Before deploying the sample, you must stop the original bundle you
intend to override. This is because the Ext sample's generated JAR
includes the original bundle source plus your modified source files.
Follow the instructions below to do this:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Connect to your portal instance using
\href{/docs/7-1/reference/-/knowledge_base/r/using-the-felix-gogo-shell}{Gogo
Shell}.
\item
Search for the bundle ID of the original bundle to override. To find
the \texttt{com.liferay.login.web} bundle, execute this command:
\begin{verbatim}
lb -s | grep com.liferay.login.web
\end{verbatim}
This returns output similar to this:
\begin{verbatim}
1580|Active | 10|com.liferay.login.web (4.0.5)
\end{verbatim}
Make note of the ID (e.g., \texttt{1580}).
\item
Stop the bundle:
\begin{verbatim}
stop 1580
\end{verbatim}
\end{enumerate}
Once the original bundle is stopped, deploy the Ext module. Note that
you cannot leverage Blade or Gradle's \texttt{deploy} command to do
this. The \texttt{deploy} command deploys the module to the
\texttt{osgi\textbackslash{}marketplace\textbackslash{}override} folder
by default, which does not configure Ext modules properly for usage. You
should build and copy the Ext module's JAR to the \texttt{deploy} folder
manually, or leverage Liferay Dev Studio's
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project\#liferay-dev-studio}{deployment}
feature.
\section{What API(s) and/or code components does this sample
highlight?}\label{what-apis-andor-code-components-does-this-sample-highlight-22}
This sample demonstrates how to create an Ext module and configure it to
replace a default module bundle.
\section{How does this sample leverage the API(s) and/or code
component?}\label{how-does-this-sample-leverage-the-apis-andor-code-component-22}
You can create your own Ext module project by
\begin{itemize}
\tightlist
\item
Declaring the original module name and version.
\item
Providing the source code that will replace the original.
\end{itemize}
To declare the original module in the \texttt{build.gradle} file
properly (only supports Gradle), you must specify the original module's
Bundle Symbolic Name and the original module's exact version. In this
example, this is configured like this:
\begin{verbatim}
originalModule group: "com.liferay", name: "com.liferay.login.web", version: "4.0.5"
\end{verbatim}
If you're leveraging
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}, you should put your Ext module project in the \texttt{/ext}
folder (default); you can specify a different Ext folder name in
workspace's \texttt{gradle.properties} by adding
\begin{verbatim}
liferay.workspace.ext.dir=EXT_DIR
\end{verbatim}
If you are developing an Ext module project in standalone mode (not
associated with Liferay Workspace), you must declare the Ext Gradle
plugin in your \texttt{build.gradle}:
\begin{verbatim}
apply plugin: 'com.liferay.osgi.ext.plugin'
\end{verbatim}
Then you must provide your own code intended to replace the original
one. \textbf{Be sure to mimic the original module's folder structure
when overriding its JAR.}
The following file types can be overlaid with an Ext module:
\begin{itemize}
\tightlist
\item
CSS
\item
Java
\item
JavaScript
\item
Language files (\texttt{Language.properties})
\item
Scss
\item
Soy
\item
etc.
\end{itemize}
The
\href{https://github.com/liferay/liferay-portal/blob/master/modules/sdk/gradle-plugins/src/main/java/com/liferay/gradle/plugins/LiferayOSGiExtPlugin.java}{Ext
Gradle Plugin} helps compile your code into the JAR. For example,
\texttt{.scss} files are compiled into \texttt{.css} files, which are
included in your module's JAR file artifact. This is done by the
\texttt{buildCSS} task.
\section{Where Is This Sample?}\label{where-is-this-sample-17}
There are two different versions of this sample, each built with a
different build tool:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/gradle/ext/login-web-ext}{Gradle}
\item
\href{https://github.com/liferay/liferay-blade-samples/tree/7.2/liferay-workspace/ext/login-web-ext}{Liferay
Workspace}
\end{itemize}
\chapter{Segmentation and Personalization
Reference}\label{segmentation-and-personalization-reference}
Browse this section's reference articles for additional information on
the Segmentation and Personalization framework.
\chapter{Defining Segmentation
Criteria}\label{defining-segmentation-criteria}
There are three categories for defining Segment criteria:
\begin{itemize}
\tightlist
\item
User Properties
\item
Organization Properties
\item
Session Properties
\end{itemize}
There are several types of information that can be collected by the User
Segment interface. Some data is entered in text boxes, while others use
selectors to select specific criteria or tools like a date picker. In
addition, some fields use an operator, which, depending on the specific
context lets you select the relationship between the user or agent data
and the criteria:
\begin{itemize}
\item
\emph{equals}
\item
\emph{not equals}
\item
\emph{greater than}
\item
\emph{greater than or equals}
\item
\emph{less than}
\item
\emph{less than or equals}
\item
\emph{contains}
\item
\emph{does not contain}
\end{itemize}
Depending on the nature of the criteria, the operator selection may
contain different combinations. For example, the \emph{Date} selection
described below contains options for all the above option except
\emph{contains} and \emph{does not contain}, whereas the \emph{Email
Address} selection has \emph{equals}, \emph{not equals}, \emph{contains}
and \emph{does not contain}.
In between each criteria and each category, you can define an ``and'' or
``or'' conjunction. For ``and'' all criteria must be true in order for
the criteria to be satisfied. With ``or'' it will be true if any of the
defined criteria are true. You can also mix operators to create complex
cases.
\section{User Properties}\label{user-properties}
The following are the criteria available for defining user properties:
\textbf{Date Modified:} Provides a date picker and an relationship
selector to select the date that user information was last changed
\textbf{Email Address:} Provides a text box to enter the email provided
in the user's\\
profile.
\textbf{First Name:} Enter the first name provided in the user's
profile.
\textbf{Group:} Select a site that the user is a member of.
\textbf{Job Title:} Enter the job title provided in the user's profile.
\textbf{Last Name:} Enter the last name provided in the user's profile.
\textbf{Role:} Select a role that the user is a member of.
\textbf{Screen Name:} Enter the users' screen name.
\textbf{Team:} Select a team that the user is a member of.
\textbf{User Group:} Select a user group that the user is a member of.
\textbf{User:} Select a specific user from a list.
\textbf{Name:} The full name of the user.
\section{Organization Properties}\label{organization-properties}
\textbf{Date Modified:} Enter the date that the organization information
was last modified.
\textbf{Name:} Enter the name of the organization.
\textbf{Hierarchy Path:} Enter the name of an ancestor organization.
\textbf{Organization:} Select a specific organization.
\textbf{Parent Organization:} Select a specific parent organization.
\textbf{Type:} Select the type of organization, if organization types
have been defined.
\section{Session Properties}\label{session-properties}
\textbf{Browser:} Enter a property from the browser.
\textbf{Cookies:} Enter the name of a browser cookie.
\textbf{Device Brand:} Enter the brand name of the device being used.
\textbf{Device Model:} Enter the model name of the device being used.
\textbf{Device Screen Resolution Height:} Enter the screen resolution
height value.
\textbf{Device Screen Resolution Width:} Enter the screen resolution
width value.
\textbf{Language:} Select the current Language.
\textbf{Last Sign In Date:} Select the date of the user's last sign in.
\textbf{Local Date:} Select the current date where the user is located.
\textbf{Referrer URL:} Enter the URL that the user last visited.
\textbf{Signed In:} Select whether the user is signed in.
\textbf{URL:} Enter the current URL.
\textbf{User Agent:} Enter a User Agent property.
\chapter{Tooling}\label{tooling}
You can write code for Liferay DXP using any standard toolset. Liferay
is tool-agnostic, which frees you to work with whatever you're already
productive using.
Liferay has also created its own tools that streamline Liferay DXP
development. These tools integrate with popular build environments
(e.g., Gradle, Maven, and NodeJS). They include
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}: a
command line interface used to build and manage Liferay Workspaces and
Liferay DXP projects. This CLI is intended for Gradle or Maven
development.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}: a generated Gradle/Maven environment built to hold and
manage Liferay DXP projects.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio}: an Eclipse-based IDE supporting development for Liferay
DXP.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/intellij}{Liferay
IntelliJ Plugin}: a plugin providing support for Liferay DXP
development with IntelliJ IDEA.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Liferay
Theme Generator}: a generator that creates themes, layouts templates,
and themelets for Liferay DXP development.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/js-generator}{Liferay JS
Generator}: a generator that creates JavaScript portlets with
JavaScript tooling.
\end{itemize}
Liferay also provides a plethora of
\href{/docs/7-2/reference/-/knowledge_base/r/gradle-plugins}{Gradle} and
\href{/docs/7-2/reference/-/knowledge_base/r/maven-plugins}{Maven
plugins} you can apply to your projects. Many of these are already built
into tools such as Liferay Workspace.
Want samples or predefined project templates? Liferay has you covered
with 30+
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{project
templates} and many more
\href{/docs/7-2/reference/-/knowledge_base/r/sample-projects}{project
samples}.
If you're a newbie looking for the best development tool for Liferay
DXP, or even a seasoned veteran looking for a tool you may like more
than your current setup, this section answers your tooling questions.
\chapter{Creating a Project}\label{creating-a-project}
Liferay provides many project templates you can use to generate starter
projects formatted in an opinionated way. Visit the
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{Project
Templates} reference section for more information on the available
project templates. Each project template has different configurable
options, so be sure to research a project template before generating it.
You can use your desired tool to generate a project. The following tools
are preconfigured for Liferay project generation:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/intellij}{Liferay
IntelliJ Plugin}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Maven}
\end{itemize}
It's recommended to create Liferay projects within a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}. Most tools, however, support creating projects in a
standalone environment (except for IntelliJ). Visit the appropriate
section to learn how to create a project with the highlighted tool.
\section{Blade CLI}\label{blade-cli}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Print the available project templates by executing this:
\begin{verbatim}
blade create -l
\end{verbatim}
Note the project template you want to generate; you'll use it in the
next step.
\item
Run the following command to create a Gradle project with Blade CLI:
\begin{verbatim}
blade create -t [projectTemplate] [option1] [option2] ... [optionN] [projectName]
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} If you want to generate a project for a previous version
(e.g., Liferay Portal 7.0), you can specify this using the \texttt{-v}
flag. For example, to create a project for Liferay Portal 7.0, you would
include \texttt{-v\ 7.0} in your create command sequence.
\noindent\hrulefill
The available configuration options are documented for each
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{project
template}. Run \texttt{blade\ create\ -\/-help} for the entire list of
available options.
\section{Liferay Dev Studio}\label{liferay-dev-studio}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{File} → \emph{New} → \emph{Liferay Module Project}.
\item
Specify the project name, location, build type, Liferay DXP version,
and template type.
\begin{figure}
\centering
\includegraphics{./images/liferay-project-wizard.png}
\caption{The New Liferay Module Project wizard offers project
templates for JAR and WAR-based projects.}
\end{figure}
\item
Click \emph{Next} and you're given additional configuration options
based on the project template you selected. For example, if you
selected a template that requires a component class, you must
configure it in the wizard.
\begin{figure}
\centering
\includegraphics{./images/component-class-wizard.png}
\caption{Specify your component class's details in the Portlet
Component Class Wizard.}
\end{figure}
You can specify your component class's name, package name, and its
properties. The properties you assign are the ones found in the
\texttt{@Component} annotation's \texttt{property\ =\ \{...\}}
assignment.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can also create a new component class for a pre-existing
module project. Navigate to *File* → *New* → *Liferay Component
Class*. This is a similar wizard to the previous component class wizard,
except you can select a component class template.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
Click \emph{Finish} to create your project.
\end{enumerate}
\section{Liferay IntelliJ Plugin}\label{liferay-intellij-plugin}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{File} → \emph{New} → \emph{Liferay Module}.
\begin{figure}
\centering
\includegraphics{./images/intellij-new-liferay-module.png}
\caption{Selecting \emph{Liferay Module} opens the New Liferay Modules
wizard.}
\end{figure}
\item
Select the project you want to create.
\begin{figure}
\centering
\includegraphics{./images/intellij-modules.png}
\caption{Choose the project template to create your module.}
\end{figure}
\item
Configure your project's SDK (i.e., JDK), package name, class name,
and service name, if necessary. Then click \emph{Next}.
\item
Give your project a name. Then click \emph{Finish}.
\end{enumerate}
\section{Maven}\label{maven}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Execute the following Maven command:
\begin{verbatim}
mvn archetype:generate -Dfilter=liferay
\end{verbatim}
\item
Select the archetype you want to leverage and proceed through the
configuration prompts.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} Maven projects can also be generated using Blade CLI.
Follow \hyperref[blade-cli]{Blade CLI's} project creation instructions
and insert the \texttt{-b\ maven} parameter in the Blade command.
\noindent\hrulefill
Archetypes prefixed with
\texttt{com.liferay.project.templates.{[}TYPE{]}} or
\texttt{com.liferay.faces.archetype:{[}TYPE{]}} are compatible with 7.0.
All other Liferay archetypes are legacy archetypes targeted for previous
versions of Liferay DXP.
See Maven's
\href{http://maven.apache.org/archetype/maven-archetype-plugin/generate-mojo.html}{Archetype
Generation} documentation for further details on how to modify the Maven
archetype generation process.
\chapter{Deploying a Project}\label{deploying-a-project}
Deploying a project to Liferay DXP can be completed using your tool of
choice. The following tools are preconfigured (or can be easily
configured) for Liferay project generation:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
\item
\href{https://gradle.org/}{Gradle}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/intellij}{Liferay
IntelliJ Plugin}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Maven}
\end{itemize}
The deployment process is the same across all tools; the deployment
command/action builds and deploys your project based on the build tool's
deployment configuration. For example, leveraging Blade CLI in a default
Gradle Liferay Workspace uses the underlying Gradle deployment
configuration. The build tool's deployment configuration is found by
reading the Liferay Home folder. The Liferay Home folder is
preconfigured in most cases; if it's not, ways to configure it are
included below. All tools support JAR and WAR-style project deployment.
It's recommended to deploy Liferay projects within a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}. Most tools, however, support deploying projects in a
standalone environment (except for IntelliJ). Visit the appropriate
section to learn how to deploy a project with the highlighted tool.
\section{Blade CLI}\label{blade-cli-1}
This is the recommended way to deploy Gradle and Maven projects in a
Liferay Workspace via command line. Blade CLI is leveraged by Dev Studio
and IntelliJ too.
Run this command to deploy your project:
\begin{verbatim}
blade deploy
\end{verbatim}
If you prefer not to use your underlying build tool's (Gradle or Maven)
module deployment configuration, and instead, you want to deploy
straight to Liferay DXP's OSGi container, run this command:
\begin{verbatim}
blade deploy -l
\end{verbatim}
\section{Gradle}\label{gradle}
Deploying with pure Gradle is not recommended unless you prefer to
develop outside of a Liferay Workspace. \hyperref[blade-cli]{Blade CLI}
is a better tool for deploying Liferay Gradle projects in most cases.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Apply the Liferay Gradle plugin in your project's
\texttt{build.gradle} file:
\begin{verbatim}
apply plugin: "com.liferay.plugin"
\end{verbatim}
\item
Extend the Liferay extension object to set your Liferay Home and
\texttt{deploy} folder:
\begin{verbatim}
liferay {
liferayHome = "../../../../liferay-ce-portal-7.1.1-ga2"
deployDir = file("${liferayHome}/deploy")
}
\end{verbatim}
\item
Run this command to deploy your project:
\begin{verbatim}
./gradlew deploy
\end{verbatim}
\end{enumerate}
\section{Liferay Dev Studio}\label{liferay-dev-studio-1}
These steps assume you've
\href{/docs/7-2/reference/-/knowledge_base/r/installing-a-liferay-server-in-dev-studio}{installed
a Liferay server in Dev Studio}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click the server from the Servers window and select \emph{Add
and Remove\ldots{}}.
\item
Add the project(s) you'd like to deploy from the Available window to
the Configured window. Then click \emph{Finish}.
\begin{figure}
\centering
\includegraphics{./images/add-and-remove-ide.png}
\caption{Using the this deployment method is convenient when deploying
multiple projects.}
\end{figure}
\item
Verify your project builds, deploys, and starts successfully by
viewing the results in the Console window.
\end{enumerate}
Dev Studio's deployment mechanism executes the \texttt{watch} task
behind the scenes. For more information on what to expect from the
\texttt{watch} task, see
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{this article}.
\section{Liferay IntelliJ Plugin}\label{liferay-intellij-plugin-1}
These steps assume you've
\href{/docs/7-2/reference/-/knowledge_base/r/installing-a-server-in-intellij}{installed
a Liferay server in IntelliJ}. A configured Liferay Workspace is
required to create and deploy Liferay projects in IntelliJ.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click your project from within the Liferay Workspace folder
structure and select \emph{Liferay} → \emph{Deploy}.
This automatically loads a build progress window viewable at the
bottom of your IntelliJ instance.
\begin{figure}
\centering
\includegraphics{./images/intellij-project-build.png}
\caption{Verify that your project built successfully.}
\end{figure}
\item
Verify that your project builds successfully from the build progress
window. Then navigate back to your server's window and confirm it
starts in your configured Liferay DXP instance.
\end{enumerate}
\section{Maven}\label{maven-1}
If you're developing your project in a Liferay Workspace, skip to step
3.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following plugin configuration to your Liferay Maven project's
parent \texttt{pom.xml} file.
\begin{verbatim}
com.liferay
com.liferay.portal.tools.bundle.support
3.4.1
deploy
deploy
pre-integration-test
\end{verbatim}
This POM configuration applies Liferay's
\href{/docs/7-2/reference/-/knowledge_base/r/bundle-support-plugin}{Bundle
Support plugin}. This plugin is applied in Liferay Workspace by
default. The Bundle Support configuration defines the
\href{https://maven.apache.org/guides/mini/guide-configuring-plugins.html\#Using_the_executions_Tag}{executions}
tag, which configures the Bundle Support plugin to run during the
\texttt{pre-integration-test} phase of your Maven project's build
lifecycle. The
\href{http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html\#A_Build_Phase_is_Made_Up_of_Plugin_Goals}{\texttt{deploy}}
goal is defined for that lifecycle phase.
\item
Define your Liferay home folder in your POM. You can do this by adding
the following logic within the \texttt{plugin} tags, but outside of
the \texttt{execution} tags:
\begin{verbatim}
C:/liferay/liferay-ce-portal-7.1-ga1
\end{verbatim}
\item
Run this command to deploy your project:
\begin{verbatim}
mvn verify
\end{verbatim}
\end{enumerate}
\chapter{Blade CLI}\label{blade-cli-2}
\href{https://github.com/liferay/liferay-blade-cli/}{Blade CLI} is a
command line tool that makes it easy for Liferay developers to create,
manage, and deploy Liferay projects (Gradle or Maven). Blade CLI can
\begin{itemize}
\tightlist
\item
Create Liferay projects usable in any IDE or development environment
\item
Create/manage Liferay DXP instances
\item
Deploy Liferay projects
\item
And more
\end{itemize}
The table below describes all Blade CLI commands for the latest Blade
CLI release.
Command \textbar{} Description \texttt{convert} \textbar{} Converts a
Plugins SDK plugin project to a Gradle Workspace project. See the
\href{/docs/7-1/reference/-/knowledge_base/r/migrating-traditional-plugins-to-workspace-web-applications\#running-the-migration-command}{Running
the Migration Command} command for details. \texttt{create} \textbar{}
Creates a new Liferay project from available templates. See the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project\#blade-cli}{Creating
a Project section for Blade CLI} for more information. \texttt{deploy}
\textbar{} Builds and deploys projects to Liferay DXP. See the
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project\#blade-cli}{Deploying
a Project section for Blade CLI} for more information.
\texttt{extension\ install} \textbar{} Installs an extension into Blade
CLI. \texttt{extension\ uninstall} \textbar{} Uninstalls an extension
from Blade CLI. \texttt{gw} \textbar{} Executes a Gradle command using
the Gradle Wrapper, if detected (e.g., \texttt{blade\ gw\ tasks}).
\texttt{help} \textbar{} Provides information for Blade CLI's commands.
\texttt{init} \textbar{} Initializes a new Liferay Workspace. See the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-liferay-workspace\#blade-cli}{Creating
a Liferay Workspace} article for more information. \texttt{samples}
\textbar{} Generates a sample project. See the
\href{/docs/7-2/reference/-/knowledge_base/r/generating-project-samples-with-blade-cli}{Generating
Project Samples with Blade CLI} article for more information.
\texttt{server\ init} \textbar{} Initializes the Liferay server
configured in Liferay Workspace's \texttt{gradle.properties} file. Set
the \texttt{liferay.workspace.bundle.url} property to configure the
server to initialize. \texttt{server\ start} \textbar{} Starts the
Liferay server in the background. You can add the \texttt{-d} flag to
start the server in debug mode. Debug mode can be customized by adding
the \texttt{-p} tag to set the custom remote debugging port (defaults
are \texttt{8000} for Tomcat and \texttt{8787} for Wildfly) and/or the
boolean \texttt{-s} tag to set whether you want to suspend the started
server until the debugger is connected. See the
\href{/docs/7-2/reference/-/knowledge_base/r/managing-your-liferay-server-with-blade-cli}{Managing
Your Liferay Server with Blade CLI} article for more information.
\texttt{server\ stop} \textbar{} Stops the Liferay server.
\texttt{server\ run} \textbar{} Starts the Liferay server in the
foreground. See the \texttt{server\ start} property for more
information. \texttt{sh} \textbar{} Connects to Liferay DXP, executes
succeeding Gogo command, and returns output. For example,
\texttt{blade\ sh\ lb} lists Liferay DXP's bundles using the Gogo shell.
See the
\href{/docs/7-2/reference/-/knowledge_base/r/managing-your-liferay-server-with-blade-cli}{Managing
Your Liferay Server with Blade CLI} article for more information.
\texttt{update} \textbar{} Updates Blade CLI to the latest version. See
the
\href{/docs/7-2/reference/-/knowledge_base/r/updating-blade-cli}{Updating
Blade CLI} article for details. \texttt{upgradeProps} \textbar{}
Analyzes your old \texttt{portal-ext.properties} and your newly
installed 7.x server to show you properties moved to OSGi configuration
files or removed from the product. \texttt{watch} \textbar{} Watches for
changes to a deployed project and automatically redeploys it when
changes are detected. This command does not rebuild your project and
copy it to Portal every time a change is detected, but rather, installs
it into the runtime as a reference. This means that the Portal does not
make a cached copy of the project. This allows the Portal to see changes
that are made to your project's files immediately. When you cancel the
\texttt{watch} task, your module is uninstalled automatically. The
\texttt{blade\ deploy\ -w} command works similarly to
\texttt{blade\ watch}, except it manually recompiles and deploys your
project every time a change is detected. This causes slower update
times, but does preserve your deployed project in Portal when it's shut
down. \texttt{version} \textbar{} Displays version information about
Blade CLI.
For information on command options, run the command with the
\texttt{-\/-help} flag (e.g., \texttt{blade\ samples\ -\/-help}).
Continue on to learn about leveraging Blade CLI to create and test
Liferay DXP instances and projects.
\chapter{Installing Blade CLI}\label{installing-blade-cli}
You can install Blade CLI using the Liferay Project SDK installer. This
installs JPM and Blade CLI into your user home folder and optionally
initializes a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} folder. Do not use the Liferay Project SDK installer to
update Blade CLI; instead, follow the instructions for
\href{/docs/7-2/reference/-/knowledge_base/r/updating-blade-cli}{updating
Blade CLI}.
If you must configure proxy settings for Blade CLI, follow the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-blade-cli-with-proxy-requirements}{Installing
Blade CLI with Proxy Requirements} instructions instead.
Follow the steps below to download and install Blade CLI:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Download the latest
\href{https://sourceforge.net/projects/lportal/files/Liferay\%20IDE/}{Liferay
Project SDK installer} that corresponds with your operating system
(e.g., Windows, MacOS, or Linux). The Project SDK installer is listed
under \emph{Liferay IDE}, so the folder versions are based on IDE
releases. You can select an installer without Dev Studio DXP if you
don't intend to use it. The Project SDK installer is available for
versions 3.2.0+. Do \textbf{not} select the large green download
button; this downloads Liferay Portal instead.
\item
Run the installer.
\item
Select the Java Runtime to use with Blade CLI. Then click \emph{OK}.
\item
Click \emph{Next} to step through the installer's introduction.
\item
If you'd like to initialize a Liferay Workspace, you can set its
location.
\begin{figure}
\centering
\includegraphics{./images/blade-installer-workspace-init.png}
\caption{Determine where your Liferay Workspace should reside, if you
want one.}
\end{figure}
Select \emph{Don't initialize Liferay Workspace directory} if you only
want to install Blade CLI. Then click \emph{Next}.
\item
If you initialized a Liferay Workspace folder, an additional option
appears for selecting the Liferay product type to use with your
workspace. Choose the product type and click \emph{Next}.
\begin{figure}
\centering
\includegraphics{./images/installer-workspace-type.png}
\caption{Select the product version you'll use with your Liferay
Workspace.}
\end{figure}
\item
Click \emph{Next} to begin installing Blade CLI/Liferay Workspace on
your computer.
\end{enumerate}
That's it! Blade CLI is installed on your machine! If you specified a
location to initialize a Liferay Workspace folder, that is also
available.
If Blade CLI doesn't work properly on your machine, visit the
\href{/docs/7-2/reference/-/knowledge_base/r/common-errors-with-blade-cli}{Common
Errors with Blade CLI} article for solutions to common problems.
\chapter{Installing Blade CLI with Proxy
Requirements}\label{installing-blade-cli-with-proxy-requirements}
If you have proxy server requirements and want to use Blade CLI, you
must configure your http(s) proxy for it using JPM:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Install JPM and Blade CLI using the Liferay Project SDK installer.
Read the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-blade-cli}{Installing
Blade CLI} tutorial for more details.
\item
Execute the following command to configure your proxy requirements for
Blade CLI:
\begin{verbatim}
jpm command --jvmargs "-Dhttp(s).proxyHost=[your proxy host] -Dhttp(s).proxyPort=[your proxy port]" jpm
\end{verbatim}
\end{enumerate}
Excellent! You've configured Blade CLI with your proxy settings using
JPM.
\chapter{Managing Your Liferay Server with Blade
CLI}\label{managing-your-liferay-server-with-blade-cli}
You can manage a Liferay server using Blade CLI. Managing a server with
Blade CLI should be done in a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}.
Blade CLI has commands for installing, starting, stopping, inspecting,
and modifying a Liferay server:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make sure you've created a Liferay Workspace. See the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-liferay-workspace\#blade-cli}{Creating
a Liferay Workspace} article for more information.
\item
Initialize a Liferay server by running
\begin{verbatim}
blade server init
\end{verbatim}
This downloads the Liferay DXP bundle set in your workspace's
\texttt{gradle.propeties} file. See the
\href{/docs/7-2/reference/-/knowledge_base/r/adding-a-liferay-bundle-to-liferay-workspace}{Adding
a Liferay Bundle to Workspace} article for more information.
You can initialize a server based on a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace\#testing-projects}{defined
environment} by running the following command:
\begin{verbatim}
blade server init --environment [ENVIRONMENT]
\end{verbatim}
For example, you could pass in the \texttt{uat} variable to generate a
bundle with the configs set in the \texttt{configs/uat} workspace
folder.
\item
Start your Liferay server (Tomcat or Wildfly/JBoss) by running
\begin{verbatim}
blade server start
\end{verbatim}
This starts the server in the background. You can tail the logs by
adding the \texttt{-t} flag. If you prefer starting the server in the
foreground, run \texttt{blade\ server\ run}. Additionally, if you
prefer starting the server in debug mode, add the \texttt{-d} flag.
See the \href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade
CLI} article for additional flags you can set when starting your
Liferay server.
\item
Examine your server's OSGi container by using Blade CLI's \texttt{sh}
command, which provides access to your server using the Felix Gogo
shell. For example, to check if you successfully deployed your
application from the previous section, you could run:
\begin{verbatim}
blade sh lb
\end{verbatim}
Your output lists a long list of modules that are active/installed in
your server's OSGi container.
\begin{figure}
\centering
\includegraphics{./images/blade-sh.png}
\caption{Blade CLI accesses the Gogo shell script to run the
\texttt{lb} command.}
\end{figure}
You can run any Gogo command using \texttt{blade\ sh}. This command
requires
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Developer
Mode} to be enabled. Developer Mode is enabled in workspace by
default. See the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Using
the Felix Gogo Shell} section for more information on this tool.
\item
Once you're finished modifying your Liferay bundle, you can package it
as a sharable file by running this command:
\begin{verbatim}
blade gw distBundle[Zip|Tar]
\end{verbatim}
This lets you create a ZIP or TAR file to share with others. This
option is only available with Gradle at this time. The above command
leverages Blade CLI's \texttt{gw} option, which executes the project's
Gradle wrapper.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can avoid deploying a module inside your workspace's
`modules/` folder when `distBundle[Zip|Tar]` is called by adding the
following snippet to your workspace's `build.gradle` file:
```groovy
distBundle {
exclude "com.liferay.jsp.spy*.jar"
}
```
You can replace the JAR name above with the module JAR you want to exclude.
This is useful for those who want to have a module in their workspace that
is used for development or debug purposes only, and it should not be deployed
to production. This works for Gradle builds only at this time.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\item
Turn off your Liferay server:
\begin{verbatim}
blade server stop
\end{verbatim}
\end{enumerate}
To reference all of Blade CLI's available options, see the
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
article.
Awesome! You learned how to interact with Liferay DXP using Blade CLI.
\chapter{Generating Project Samples with Blade
CLI}\label{generating-project-samples-with-blade-cli}
Liferay provides many useful
\href{https://github.com/liferay/liferay-blade-samples}{sample projects}
for those interested in learning best practices for Liferay DXP
projects. You can learn more about these samples by visiting
\href{/docs/7-2/reference/-/knowledge_base/r/sample-projects}{Sample
Projects}.
Rather than cloning the repository, you can generate these samples using
Blade CLI for convenience.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
List the available sample projects:
\begin{verbatim}
blade samples
\end{verbatim}
Note the sample project you want to generate; you'll use it in the
next step.
\item
Run the following command to generate a sample project:
\begin{verbatim}
blade samples
\end{verbatim}
For example, to generate the
\href{https://github.com/liferay/liferay-blade-samples/tree/master/gradle/apps/ds-portlet}{portlet-ds}
sample, execute
\begin{verbatim}
blade samples ds-portlet
\end{verbatim}
The sample is generated in the current folder.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} Interested in generating legacy versions of Blade
samples? Pass in the \texttt{-v} param followed by the Liferay DXP
version to target. For example,
\begin{verbatim}
blade samples -v 7.0 ds-portlet
\end{verbatim}
\noindent\hrulefill
Awesome! You've successfully generated a Liferay sample project using
Blade CLI!
\chapter{Updating Blade CLI}\label{updating-blade-cli}
Blade CLI is updated frequently, so you should update your Blade CLI
environment for new features. You can check the released versions of
Blade CLI on Nexus by inspecting the
\href{https://repository-cdn.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/blade/com.liferay.blade.cli/}{\texttt{com.liferay.blade.cli}}
artifact. You can check your current installed version by running
\texttt{blade\ version}.
To update your Blade CLI installation to the latest stable version, run
\begin{verbatim}
blade update
\end{verbatim}
Although Blade CLI is frequently released, if you want bleeding edge
features not yet available, you can install the latest snapshot version:
\begin{verbatim}
blade update -s
\end{verbatim}
This pulls the latest snapshot version of Blade CLI and installs it to
your local machine. Running \texttt{blade\ version} after installing a
snapshot displays output similar to this:
\begin{verbatim}
blade version 3.3.1.SNAPSHOT201811301746
\end{verbatim}
Be careful; snapshot versions are unstable and should only be used for
experimental purposes.
Awesome! You've successfully learned how to update Blade CLI.
\chapter{Converting Plugins SDK Projects with Blade
CLI}\label{converting-plugins-sdk-projects-with-blade-cli}
Blade CLI can automatically migrate a Plugins SDK project to a Liferay
Workspace. During the process, the Ant-based Plugins SDK project is
copied to the applicable workspace folder based on its project type
(e.g., \texttt{wars}) and is converted to a Gradle-based Liferay
Workspace project. This drastically speeds up the migration process when
upgrading to a Liferay Workspace from a legacy Plugins SDK.
\noindent\hrulefill
\textbf{Note:} There is no Maven command for the migration process yet,
so you must complete it manually for Maven-based workspaces.
\noindent\hrulefill
To copy your Plugins SDK project and convert it to Gradle, use the Blade
\texttt{convert} command:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the root folder of your workspace in a command line tool.
\item
Execute the following command:
\begin{verbatim}
blade convert -s [PLUGINS_SDK_PATH] [PLUGINS_SDK_PROJECT_NAME]
\end{verbatim}
You must provide the path of the Plugins SDK your project resides in
and the project name you want to convert. If you prefer converting all
the Plugins SDK projects at once, replace the project name variable
with \texttt{-a} (i.e., specifying all plugins).
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** If the `convert` task doesn't work as described above, you may
need to update your Blade CLI version. See the
[Updating Blade CLI](/docs/7-2/reference/-/knowledge_base/r/updating-blade-cli)
article for more information.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
This Gradle conversion process also works for themes; they're converted to
automatically leverage NodeJS. If you're converting a Java-based theme, add
the `-t` option to your command too. This will incorporate the
[Theme Builder](/docs/7-2/reference/-/knowledge_base/r/theme-builder-gradle-plugin)
Gradle plugin for the theme instead. For more information on upgrading
6.2 themes, see the
[Upgrade a 6.2 Theme to 7.2](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-6-2-themes-to-7-2).
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} When converting a Service Builder project, the
\texttt{convert} task automatically extracts the project's service
interfaces and implementations into OSGi modules (i.e., \emph{-impl and
}-api) and places them in the workspace's \texttt{modules} folder. Your
portlet and controller logic remain a WAR and reside in the
\texttt{wars} folder.
\noindent\hrulefill
Your project is successfully converted to a Gradle-based workspace
project! Great job!
\chapter{Extending Blade CLI}\label{extending-blade-cli}
You can extend Blade in three different ways:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-custom-commands-for-blade-cli}{Creating
Custom Commands for Blade CLI}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-custom-project-templates-for-blade-cli}{Creating
Custom Project Templates for Blade CLI}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-new-extensions-for-blade-cli}{Installing
New Extensions for Blade CLI}
\end{itemize}
There are a few use cases to consider when extending Blade CLI. For
example, if you only want to add a new command that applies globally to
all types of workspaces, you can create and install a new custom command
as explained in the links above.
Alternatively, you may want a set of custom commands that only apply to
a specific workspace environment. Normally, Liferay developers who use
Blade CLI run a series of Blade commands that make sense in the
\emph{default} Liferay Workspace. What if the workspace, however, should
support a containerized environment (e.g., Docker) or some other
specialized environment? The commands used in the development workflow
must complete the workflow differently.
To customize Blade CLI's development workflow, you must create a Blade
\emph{profile}. Blade profiles \emph{override} existing Blade commands
or add \emph{new} commands in a preserved environment that can be
applied to any Liferay Workspace. For example, \texttt{blade\ init} for
a profile \texttt{myprofile} would override the default \texttt{init}
command to do something before/after the normal \texttt{init} command.
For more information, see
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-blade-profile}{Creating
a Blade Profile}.
\noindent\hrulefill
\textbf{Note:} Blade CLI leverages the profile system internally for
Maven support. The Maven specific code is stored in an extension JAR and
embedded inside the default Blade JAR.
\noindent\hrulefill
Continue on to learn more!
\chapter{Creating Custom Commands for Blade
CLI}\label{creating-custom-commands-for-blade-cli}
To create a custom command for Blade CLI, follow these steps:
\noindent\hrulefill
\textbf{Note:} This article creates a Gradle-based command project.
These steps can be completed for a Maven-based project too.
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a generic OSGi module}.
\item
You'll leverage \href{http://jcommander.org/}{JCommander} and the
Blade CLI API to create your custom command. Add these dependencies in
your build file. For example, a \texttt{build.gradle} file's
\texttt{dependencies} block looks like this:
\begin{verbatim}
dependencies {
compileOnly group: "com.beust", name: "jcommander", version: "1.72"
compileOnly group: "com.liferay.blade", name: "com.liferay.blade.cli", version: "latest.release"
}
\end{verbatim}
\item
Build a Command class by extending the
\href{https://github.com/liferay/liferay-blade-cli/blob/master/cli/src/main/java/com/liferay/blade/cli/command/BaseCommand.java}{\texttt{BaseCommand}}
class:
\begin{verbatim}
import com.liferay.blade.cli.command.BaseCommand;
public class Hello extends BaseCommand {
@Override
public void execute() throws Exception {
HelloArgs helloArgs = getArgs();
getBladeCLI().out("Hello " + helloArgs.getName());
}
@Override
public Class getArgsClass() {
return HelloArgs.class;
}
}
\end{verbatim}
This registers your new command with Blade. You must define the
\texttt{execute()} command for all classes extending
\texttt{BaseCommand}. The \texttt{BaseCommand} class expects an
arguments class as its parameter. You'll create this next.
\item
Create a class that holds your command's arguments:
\begin{verbatim}
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.liferay.blade.cli.command.BaseArgs;
@Parameters(commandDescription = "Executes a hello command", commandNames = "hello")
public class HelloArgs extends BaseArgs {
public String getName() {
return _name;
}
@Parameter(description = "The name to say hello to", names = "--name", required = true)
private String _name;
}
\end{verbatim}
This class extends the
\href{https://github.com/liferay/liferay-blade-cli/blob/master/cli/src/main/java/com/liferay/blade/cli/command/BaseArgs.java}{\texttt{BaseArgs}}
class. Notice that the class declaration has the \texttt{@Parameters}
JCommander annotation. This sets your command's description and name.
The \texttt{@Parameter} annotation applied to the private string
\texttt{\_name} sets how the command's parameter is called and whether
it's required.
\item
Since Blade looks for custom commands using the
\texttt{com.liferay.blade.cli.command.BaseCommand} service interface,
you must use a standard JRE service loader mechanism to finish
registering your new command with Blade CLI.
Create a file named \texttt{com.liferay.blade.cli.command.BaseCommand}
in the \texttt{src/main/resources/META-INF/services/} folder. This
class should list all of your custom commands' fully qualified class
names:
\begin{verbatim}
com.liferay.extensions.sample.command.Hello
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Java's Service Loader Interface (SPI) is used to load the
fully qualified classes in the `META-INF/services` folder. You can learn
more about SPIs
[here](https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html).
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
Generate the extension's JAR file (e.g., \texttt{gradlew\ build}).
\end{enumerate}
Awesome! You've created a custom command! You can deploy multiple custom
commands in a single JAR, so you can continue adding custom command
projects to this module, if desired. See the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-new-extensions-for-blade-cli}{Installing
New Extensions} article to install the command (JAR) to Blade CLI.
You can examine a working custom command project
\href{https://github.com/liferay/liferay-blade-cli/blob/master/extensions/sample-command}{here}.
\chapter{Creating Custom Project Templates for Blade
CLI}\label{creating-custom-project-templates-for-blade-cli}
Blade comes with 32+ project templates, but many times you may feel that
those are too simple or don't fit the need for your development team.
You can create new custom project templates that fit your team's
workflow and have Blade use them instead.
\noindent\hrulefill
\textbf{Important:} An extension JAR can only contain one template. If
you have multiple templates, each must be deployed in a separate JAR.
\noindent\hrulefill
Implementing a custom project template should mimic that of a Maven
archetype. The best way to illustrate this is by visualizing a
\href{https://github.com/liferay/liferay-blade-cli/tree/master/extensions/sample-template}{sample
template}'s folder structure:
\begin{itemize}
\tightlist
\item
\texttt{src/}
\begin{itemize}
\tightlist
\item
\texttt{main/resources/}
\begin{itemize}
\tightlist
\item
\texttt{META-INF}
\begin{itemize}
\tightlist
\item
\texttt{maven}
\begin{itemize}
\tightlist
\item
\texttt{archetype-metadata.xml}
\end{itemize}
\item
\texttt{archetype-post-generate.groovy} (optional; only invoked
by Maven projects)
\end{itemize}
\item
\texttt{archetype-resources}
\begin{itemize}
\tightlist
\item
Folder structure to be generated
\end{itemize}
\end{itemize}
\end{itemize}
\item
\texttt{bnd.bnd}
\item
\texttt{{[}build.gradle\textbar{}pom.xml{]}}
\end{itemize}
You can read more about Maven archetypes and their features and
capabilities
\href{https://maven.apache.org/guides/introduction/introduction-to-archetypes.html}{here}.
To create a custom project template that can be generated using Blade
CLI, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a generic Maven archetype following the folder structure
outlined above. Follow Maven's documentation to configure the
archetype project appropriately.
\item
Open the template's \texttt{bnd.bnd} file and ensure it sets the
following configurations:
\begin{verbatim}
Bundle-Description: TEMPLATE_DESCRIPTION
Bundle-Name: TEMPLATE_NAME
Bundle-SymbolicName: SYMBOLIC_NAME
Bundle-Version: TEMPLATE_VERSION
Liferay-Versions: LIFERAY_VERSION_RANGE
-removeheaders:\
Import-Package,\
Private-Package,\
Require-Capability
\end{verbatim}
For example, a template's \texttt{bnd.bnd} could look like this:
\begin{verbatim}
Bundle-Description: Creates a Sample as a module project.
Bundle-Name: Liferay Project Templates Sample
Bundle-SymbolicName: com.liferay.project.templates.sample
Bundle-Version: 1.0.0
Liferay-Versions: [7,8)
-removeheaders:\
Import-Package,\
Private-Package,\
Require-Capability
\end{verbatim}
The \texttt{Bundle-SymbolicName} of your template JAR must have the
pattern \texttt{*.project.templates.\textless{}name\textgreater{}.*}.
The \texttt{-removeheaders} definition is a packaging requirement for
all project templates. For more information on Bnd versioning, visit
\href{https://bnd.bndtools.org/chapters/170-versioning.html}{Bnd's
official docs}.
\item
Generate the extension's JAR file (e.g., \texttt{gradlew\ build}).
\end{enumerate}
It's that easy! You've created a custom project template. See the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-new-extensions-for-blade-cli}{Installing
New Extensions} article to install the project template (JAR) to Blade
CLI.
You can examine a working custom project template
\href{https://github.com/liferay/liferay-blade-cli/blob/master/extensions/sample-template}{here}.
\chapter{Installing New Extensions for Blade
CLI}\label{installing-new-extensions-for-blade-cli}
After you've created a new extension for Blade CLI, you must install it
so it's available for use. You can learn how to create
\href{/docs/7-2/reference/-/knowledge_base/r/creating-custom-commands-for-blade-cli}{custom
commands} and
\href{/docs/7-2/reference/-/knowledge_base/r/creating-custom-project-templates-for-blade-cli}{custom
project templates} in their respective articles.
When Blade CLI starts, it looks in the user's
\texttt{\$\{user.home\}/.blade/extensions} folder for JAR files. All JAR
files are searched to see if they contain valid Blade extensions. You'll
learn how to install new extensions next.
\section{Installing a New Extension}\label{installing-a-new-extension}
To install an extension, you must move the extension JAR to the user's
\texttt{\$\{user.home\}/.blade/extensions} folder. You can do this
automatically from Blade CLI by running
\begin{verbatim}
blade extension install /path/to/my_extension.JAR
\end{verbatim}
You can verify that the extension is available by running the following
commands, depending on extension type:
\textbf{Custom Command:}
\begin{verbatim}
blade help
\end{verbatim}
\textbf{Custom Project Template:}
\begin{verbatim}
blade create -l
\end{verbatim}
Great! You've installed a new extension!
\section{Uninstalling an Extension}\label{uninstalling-an-extension}
You can uninstall a Blade extension by running this:
\begin{verbatim}
blade extension uninstall EXTENSION_NAME.jar
\end{verbatim}
This removes the extension JAR from the user's
\texttt{\$\{user.home\}/.blade/extensions} folder.
\chapter{Creating a Blade Profile}\label{creating-a-blade-profile}
There are two steps to follow when adding a new Blade profile:
\begin{itemize}
\tightlist
\item
\hyperref[creating-a-new-profile]{Creating a new profile}
\item
\hyperref[setting-a-profile]{Setting the profile in Liferay Workspace}
\end{itemize}
You'll learn how to create a profile first.
\section{Creating a New Profile}\label{creating-a-new-profile}
To create a new Blade profile, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create
a generic OSGi module}.
\item
To create a new command, create the command and arguments classes
extending
\href{https://github.com/liferay/liferay-blade-cli/blob/master/cli/src/main/java/com/liferay/blade/cli/command/BaseCommand.java}{\texttt{BaseCommand}}
and
\href{https://github.com/liferay/liferay-blade-cli/blob/master/cli/src/main/java/com/liferay/blade/cli/command/BaseArgs.java}{\texttt{BaseArgs}},
respectively, as described in the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-custom-commands-for-blade-cli}{Creating
Custom Commands} article. These classes should reside in the profile
module's \texttt{src/main/java/PACKAGE\_NAME} folder. These classes
register your command and arguments to Blade CLI.
\item
To override a default command, follow the same steps outlined
\href{/docs/7-2/reference/-/knowledge_base/r/creating-custom-commands-for-blade-cli}{here}:
\begin{itemize}
\tightlist
\item
Create a command class
\item
Create an arguments class
\item
Define your commands' fully qualified class names for the service
loader
\end{itemize}
Instead of extending the
\href{https://github.com/liferay/liferay-blade-cli/blob/master/cli/src/main/java/com/liferay/blade/cli/command/BaseCommand.java}{\texttt{BaseCommand}}
and
\href{https://github.com/liferay/liferay-blade-cli/blob/master/cli/src/main/java/com/liferay/blade/cli/command/BaseArgs.java}{\texttt{BaseArgs}},
classes, however, extend the command/arguments classes defined for the
command you intend to override. Make sure to also set the
\texttt{@Parameters} annotation's \texttt{commandNames} argument to
the command to override.
For example, if you intend to override the default \texttt{deploy}
command, your arguments class declaration would look like this:
\begin{verbatim}
@Parameters(commandDescription = "Overridden Deploy Command", commandNames = "deploy")
public class OverriddenArgs extends DeployArgs {
}
\end{verbatim}
The corresponding command class override would look like this:
\begin{verbatim}
public class OverriddenCommand extends BaseCommand {
@Override
public void execute() throws Exception {
OverriddenArgs args = getArgs();
getBladeCLI().out("OverriddenCommand says " + args.isWatch());
}
@Override
public Class getArgsClass() {
return OverriddenArgs.class;
}
}
\end{verbatim}
You can search for the default command/arguments classes
\href{https://github.com/liferay/liferay-blade-cli/tree/master/cli/src/main/java/com/liferay/blade/cli/command}{here}.
\item
To associate a command to your new profile, set the
\href{https://github.com/liferay/liferay-blade-cli/blob/master/cli/src/main/java/com/liferay/blade/cli/command/BladeProfile.java}{\texttt{BladeProfile}}
annotation in your command class:
\begin{verbatim}
@BladeProfile("myprofile")
public class NewCommand extends BaseCommand {
}
\end{verbatim}
The annotation's parameter should specify the profile you want to
associate the command with (e.g., \texttt{myprofile}).
\end{enumerate}
Excellent! You've created a new Blade profile and learned how to add new
commands or override default commands by leveraging the profile.
\noindent\hrulefill
\textbf{Note:} Command classes spanning multiple JARs can use/share the
same profile name. For example, if you want to extend the internal
(provided) \texttt{maven} profile extension with new commands, you can
do it externally the same way you'd create a new profile.
\noindent\hrulefill
You can reference the
\href{https://github.com/liferay/liferay-blade-cli/tree/master/extensions/sample-profile}{sample
profile project} to examine a new command and overridden command's setup
in a custom profile.
Next, you'll learn how to set your new profile for use in a Liferay
Workspace.
\section{Setting a Profile}\label{setting-a-profile}
To set your new Blade profile in a Liferay Workspace, open the
\texttt{\$\{workspaceDir\}/.blade.properties} file and set the
\texttt{profile.name} property to your profile name:
\begin{verbatim}
profile.name=myprofile
\end{verbatim}
This specifies which Blade profile is active and uses its defined
commands. The default setting is \texttt{gradle}. You can also set this
property to \texttt{maven} out-of-the-box, which is applied for
Maven-based workspaces. You can only set one profile for a workspace.
You can specify the Blade profile for a workspace when initializing it
too. This is done by passing the profile name as an argument when
creating the workspace:
\begin{verbatim}
blade init -P [profile-name] [workspace-name]
\end{verbatim}
For example, if you execute the following command:
\begin{verbatim}
blade init -P myprofile my-new-custom-workspace
\end{verbatim}
Your \texttt{my-new-custom-workspace} has the following properties set
in its \texttt{\$\{workspaceDir\}/.blade.properties} file:
\begin{verbatim}
liferay.version.default=7.2
profile.name=myprofile
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} The \texttt{-P} profile parameter can be used for any
command to specify the profile to use for that command. This is helpful
if you want to run a command not associated with the workspace's current
profile.
\noindent\hrulefill
Awesome! You've set your new Blade profile!
\chapter{Common Errors with Blade
CLI}\label{common-errors-with-blade-cli}
If Blade CLI isn't working as expected, you may find answers here.
The following issues are documented:
\begin{itemize}
\tightlist
\item
\hyperref[the-blade-command-is-not-available-in-my-cli]{The blade
command is not available in my CLI}
\item
\hyperref[i-cant-update-my-blade-cli-version]{I can't update my Blade
CLI version}
\end{itemize}
Visit the appropriate section to learn more.
\section{The blade command is not available in my
CLI}\label{the-blade-command-is-not-available-in-my-cli}
The Liferay Project SDK installer attempts to add JPM to your path. For
Windows, it uses the Windows registry. For Mac/Linux, it updates
\texttt{.bashrc} or \texttt{.zshrc}.
At a minimum, Mac/Linux users must open a new shell after the installer
finishes for the new features to be available. If, however, you're using
a different shell (e.g., Korn, csh) or you've customized your CLI via
\texttt{.profile} or some other configuration file, you must add JPM to
your path manually.
To add JPM's required \texttt{bin} folder, execute the appropriate
command based on your operating system.
macOS:
\begin{verbatim}
echo 'export PATH="$PATH:$HOME/Library/PackageManager/bin"' >> ~/.bash_profile
\end{verbatim}
Linux:
\begin{verbatim}
echo 'export PATH="$PATH:$HOME/jpm/bin"' >> ~/.bash_profile
\end{verbatim}
Once you open a new shell, the \texttt{blade} command should be
available.
\section{I can't update my Blade CLI
version}\label{i-cant-update-my-blade-cli-version}
If you run \texttt{blade\ version} after updating, but don't see the
expected version installed, you may have two separate Blade CLI
installations on your machine. This is typically caused if you installed
an earlier version of Blade CLI and then used the Liferay Project SDK
installer (at any time prior) to update the older Blade CLI instance.
This is not recommended. Doing this installs Blade CLI in the global and
user home folder of your machine. The latest Blade CLI update process
installs to your user home folder, so you must delete the legacy Blade
files in your global folder, if present. To do this, navigate to your
\texttt{GLOBAL\_FOLDER/JPM4J} folder and delete
\begin{itemize}
\tightlist
\item
\texttt{/bin/blade}
\item
\texttt{/commands/blade}
\end{itemize}
The newest Blade CLI installation in your user home folder is now
recognized and available.
\chapter{Liferay Dev Studio}\label{liferay-dev-studio-2}
Liferay Dev Studio is an extension for the
\href{https://www.eclipse.org/ide/}{Eclipse} platform for developing
Liferay DXP plugins. It works with build tools such as Gradle and Maven
and configuration tools like BndTools.
Dev Studio makes Liferay development easier by providing an intuitive
GUI. It contains
\begin{itemize}
\tightlist
\item
Liferay-specific wizards for creating/developing Liferay DXP projects.
\item
Liferay server management and project testing capabilities.
\item
Editors for Service Builder files, workflow definitions, POM files,
and more.
\item
Snippets for tag libraries
\item
etc.
\end{itemize}
Here you'll learn how to
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-liferay-dev-studio}{Install
Dev Studio}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/setting-proxy-requirements-for-dev-studio}{Set
Proxy Requirements}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-a-liferay-server-in-dev-studio}{Install
a Liferay server}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/importing-projects-in-dev-studio}{Import
a Liferay project}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-gogo-shell-in-dev-studio}{Use
the Gogo Shell}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/searching-product-source-in-dev-studio}{Search
Liferay DXP source}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/debugging-product-source-in-dev-studio}{Debug
Liferay DXP source}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/updating-liferay-dev-studio}{Update
Dev Studio}
\end{itemize}
You can also find
\href{/docs/7-2/reference/-/knowledge_base/r/gradle-in-dev-studio}{Gradle}
and
\href{/docs/7-2/reference/-/knowledge_base/r/maven-in-dev-studio}{Maven}
reference articles that highlight popular use cases for those respective
build tools in Dev Studio.
For help
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project\#liferay-dev-studio}{creating}
and
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project\#liferay-dev-studio}{deploying}
projects with Dev Studio, or
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-liferay-workspace\#dev-studio}{creating
a Liferay Workspace}, visit their respective articles.
Let Dev Studio aid in your conquest for Liferay DXP development.
Continue on to learn how!
\chapter{Installing Liferay Dev
Studio}\label{installing-liferay-dev-studio}
Liferay Dev Studio is a plugin for Eclipse that brings many
Liferay-specific features to the table. You can install it into your
existing Eclipse environment, or Liferay provides a bundled version
included in its Project SDK. Here you'll learn the different methods
available for installing Dev Studio:
\begin{itemize}
\tightlist
\item
\hyperref[install-the-dev-studio-bundle]{install the Dev Studio bundle
from scratch}
\item
\hyperref[install-dev-studio-into-eclipse]{install Dev Studio into an
existing Eclipse instance using an update URL}
\item
\hyperref[install-dev-studio-into-eclipse-from-a-zip-file]{install Dev
Studio into an existing Eclipse instance using a ZIP file}
\end{itemize}
\textbf{Important:} If you're installing Dev Studio into an existing
Eclipse environment, you must be on Eclipse Photon or newer. For
instructions on upgrading to Photon, see Eclipse's
\href{https://wiki.eclipse.org/FAQ_How_do_I_upgrade_Eclipse_IDE\%3F\#Upgrading_existing_Eclipse_IDE_and_Installed_Features_to_newer_release}{upgrade
documentation}. With this particular upgrade, you should also deactivate
the current available update sites in the \emph{Window} →
\emph{Preferences} → \emph{Install/Update} → \emph{Available Software
Sites} menu to ensure a successful upgrade (e.g., Oxygen).
\section{Install the Dev Studio
Bundle}\label{install-the-dev-studio-bundle}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Download and install \href{http://java.oracle.com}{Java}. Liferay runs
on Java, so you'll need it to run everything else. Because you'll be
developing apps for Liferay DXP in Dev Studio, the Java Development
Kit (JDK) is required. It is an enhanced version of the Java
Environment used for developing new Java technology. You can download
the Java SE JDK from the Java
\href{http://www.oracle.com/technetwork/java/javase/downloads/index.html}{Downloads}
page.
\item
Download Liferay's latest
\href{https://sourceforge.net/projects/lportal/files/Liferay\%20IDE/}{Project
SDK with Dev Studio}. Go to the
\href{https://customer.liferay.com/group/customer/downloads}{Downloads}
page in Liferay's Help Center. Select \emph{Developer Tools} in the
Product drop-down and \emph{Developer Studio} for the file type. Then
select the executable that correlates to your operating system.
The Project SDK includes Dev Studio DXP,
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}, and
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}.
\item
Run the Project SDK executable and step through the installer to
install everything to your machine. For help with setting up proxy
settings (if necessary), see the
\href{/docs/7-2/reference/-/knowledge_base/r/setting-proxy-requirements-for-dev-studio}{Dev
Studio Proxy Settings} and
\href{/docs/7-2/reference/-/knowledge_base/r/setting-proxy-requirements-for-liferay-workspace}{Liferay
Workspace Proxy Settings} articles for more information.
\end{enumerate}
Congratulations! You've installed Liferay Dev Studio! It's now available
in the Project SDK folder's \texttt{liferay-developer-studio}. To run
Dev Studio, execute the \texttt{DeveloperStudio} executable. A Liferay
Workspace has also been initialized in that same folder.
\section{Install Dev Studio into
Eclipse}\label{install-dev-studio-into-eclipse}
If you already have an Eclipse environment that you're using for other
things, it's easy to add Dev Studio to your existing Eclipse
installation.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In Eclipse, select \emph{Help} → \emph{Install New Software}.
\item
In the \emph{Work with} field, copy in the following URL:
https://releases.liferay.com/tools/ide/latest/stable/
\item
You'll see the Liferay Dev Studio components in the list below. Check
them off and click \emph{Next}.
\item
Accept the terms of the agreements and click \emph{Next}, and Dev
Studio is installed. Like other Eclipse plugins you'll have to restart
Eclipse to enable it.
\end{enumerate}
\section{Install Dev Studio into Eclipse from a ZIP
File}\label{install-dev-studio-into-eclipse-from-a-zip-file}
To install Liferay Dev Studio into Eclipse from a Zip file, follow these
steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to the
\href{https://community.liferay.com/en_GB/project/-/asset_publisher/TyF2HQPLV1b5/content/ide-installation-instructions}{Dev
Studio} downloads page. Under \emph{Other Downloads}, select the
\emph{Liferay IDE {[}version{]} Archived Update-site} option to
download it.
\item
In Eclipse, go to \emph{Help} → \emph{Install New Software\ldots{}}.
\item
In the \emph{Add} dialog, click the \emph{Archive} button and browse
to the location of the downloaded Dev Studio Zip file.
\item
You'll see the Dev Studio components in the list below. Check them off
and click \emph{Next}.
\item
Accept the terms of the agreements and click \emph{Next}, and Liferay
Dev Studio is installed. Like other Eclipse plugins you'll have to
restart Eclipse to enable it.
\end{enumerate}
Awesome! You've installed Liferay Dev Studio. Now you can begin Liferay
development using a popular and supported IDE.
\chapter{Setting Proxy Requirements for Dev
Studio}\label{setting-proxy-requirements-for-dev-studio}
If you have proxy server requirements and want to configure your http(s)
proxy\\
to work with Liferay Dev Studio, follow the instructions below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to Eclipse's \emph{Window} → \emph{Preferences} →
\emph{General} → \emph{Network Connections} menu.
\item
Set the \emph{Active Provider} drop-down selector to \emph{Manual}.
\item
Under \emph{Proxy entries}, configure both proxy HTTP and HTTPS by
clicking the field and selecting the \emph{Edit} button.
\begin{figure}
\centering
\includegraphics{./images/ide-network-connections.png}
\caption{You can configure your proxy settings in Dev Studio's Network
Connections menu.}
\end{figure}
\item
For each schema (HTTP and HTTPS), enter your proxy server's host,
port, and authentication settings (if necessary). Do not leave
whitespace at the end of your proxy host or port settings.
\item
Once you've configured your proxy entry, click \emph{Apply and Close}.
\end{enumerate}
If you're working with a Liferay Workspace in Dev Studio, you'll need to
configure your proxy settings for that environment too. See the
\href{/docs/7-2/reference/-/knowledge_base/r/setting-proxy-requirements-for-liferay-workspace}{Setting
Proxy Requirements for Liferay Workspace} article for more details.
Awesome! You've successfully configured Dev Studio's proxy settings!
\section{Additional Proxy Settings}\label{additional-proxy-settings}
Some Eclipse plugins do not properly check the \texttt{core.net} proxy
infrastructure when setting proxy settings via \emph{Window} →
\emph{Preferences} → \emph{General} → \emph{Network Connections}.
Therefore, you may need to configure additional proxy settings.
To do so, open the \texttt{eclipse.ini} file associated with your
Eclipse installation and add the following entries:
\begin{verbatim}
-vmargs
-Dhttp.proxyHost=www.somehost.com
-Dhttp.proxyPort=1080
-Dhttp.proxyUser=userId
-Dhttp.proxyPassword=somePassword
-Dhttps.proxyHost=www.somehost.com
-Dhttps.proxyPort=1080
-Dhttps.proxyUser=userId
-Dhttps.proxyPassword=somePassword
\end{verbatim}
After saving the file, restart Eclipse. Now your additional proxy
settings are applied!
\chapter{Installing a Liferay Server in Dev
Studio}\label{installing-a-liferay-server-in-dev-studio}
Dev Studio offers a single GUI for managing a Liferay server and its
deployed projects. A server is installed and managed from the Servers
view (lower left corner in Eclipse).
For reference, here's how the Dev Studio server buttons work with your
Liferay DXP instance:
\begin{itemize}
\tightlist
\item
\emph{Start} (\includegraphics{./images/icon-start-server.png}):
Starts the server.
\item
\emph{Stop} (\includegraphics{./images/icon-stop-server.png}): Stops
the server.
\item
\emph{Debug} (\includegraphics{./images/icon-debug-server.png}):
Starts the server in debug mode. For more information on debugging in
Dev Studio, see the
\href{/docs/7-2/reference/-/knowledge_base/r/debugging-product-source-in-dev-studio}{Debugging
Liferay DXP source in Dev Studio} article.
\end{itemize}
Follow these steps to install your server. Note you must have already
downloaded and de-compressed the server bundle:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the Servers view, click the \emph{No Servers are available} link.
If you already have a server installed, you can install a new server
by right-clicking in the Servers view and selecting \emph{New} →
\emph{Server}. This brings up a wizard that walks you through the
process of defining a new server.
\item
Select the type of server you would like to create from the list of
available options. For a standard server, open the \emph{Liferay,
Inc.} folder and select the \emph{Liferay 7.x} option. You can change
the server name to something more unique too; this is the name
displayed in the Servers view. Then click \emph{Next}.
\begin{figure}
\centering
\includegraphics{./images/define-new-server.png}
\caption{Choose the type of server you want to create.}
\end{figure}
\textbf{Note:} If you've already configured previous Liferay servers,
you'll be provided the \emph{Server runtime environment} field, which
lets you choose previously configured runtime environments. If you
want to re-add an existing server, select one from the dropdown menu.
You can also add a new server by selecting \emph{Add}, or you can edit
existing servers by selecting \emph{Configure runtime environments}.
Once you've configured the server runtime environment, select
\emph{Finish}. If you selected an existing server, your server
installation is finished; you can skip steps 3-5.
\item
Enter a name for your server. This is the name for the Liferay DXP
runtime configuration used by Dev Studio. This is not the display name
used in the Servers tab.
\item
Browse to the installation folder of the Liferay DXP bundle. For
example,
\texttt{C:\textbackslash{}liferay-ce-portal-7.2.0-m2\textbackslash{}tomcat-9.0.10}.
\begin{figure}
\centering
\includegraphics{./images/specify-bundle-directory.png}
\caption{Specify the installation folder of the bundle.}
\end{figure}
\item
Select a runtime JRE and click \emph{Finish}. Your new server appears
under the Servers view.
\begin{figure}
\centering
\includegraphics{./images/new-server-added.png}
\caption{Your new server appears under the \emph{Servers} view.}
\end{figure}
\end{enumerate}
Congratulations! Your server is now available in Liferay Dev Studio!
\chapter{Importing Projects in Dev
Studio}\label{importing-projects-in-dev-studio}
There are two types of Liferay projects you can import into Dev Studio:
\begin{itemize}
\tightlist
\item
Liferay Module Project (this also includes WAR-style projects)
\item
Liferay Workspace Project
\end{itemize}
You cannot import Liferay projects individually that reside in a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}. You can either import a non-workspace Liferay project (or
group of projects if the parent folder is specified) or an entire
workspace project with all its Liferay projects.
To import a pre-existing Liferay project into Dev Studio, follow the
steps outlined below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click in the Project Explorer and select \emph{Import} →
\emph{Liferay Module Project}. If you're interested in importing an
entire Liferay Workspace, select the \emph{Liferay Workspace Project}
instead.
\begin{figure}
\centering
\includegraphics{./images/import-liferay-project.png}
\caption{You can import a single project or folder of projects.}
\end{figure}
Once you've selected your project(s), the project build type is
displayed.
\item
Click \emph{Finish}.
\end{enumerate}
Now your Liferay project is available from the Package Explorer.
\chapter{Using the Gogo Shell in Dev
Studio}\label{using-the-gogo-shell-in-dev-studio}
If you're using Dev Studio to develop and deploy your projects, you may
be interested in managing them after they're deployed with Dev Studio
too. You can do this with the Dev Studio's
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
Shell} feature.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click your started portal instance in the Servers view.
\item
Select \emph{Open Gogo Shell}.
\begin{figure}
\centering
\includegraphics{./images/open-gogo-shell.png}
\caption{Select \emph{Open Gogo Shell} to open a terminal window in
Dev Studio using Gogo shell.}
\end{figure}
A Gogo shell terminal appears, allowing you to enter Gogo commands to
inspect your Liferay instance and the projects deployed to it.
\item
A common use case for the Gogo Shell is verifying successful project
deployment. Enter the \texttt{lb} command to view a list of deployed
bundles. If the project status is active, then it deployed
successfully.
\begin{figure}
\centering
\includegraphics{./images/gogo-deploy-successful.png}
\caption{You can check to see if your project deployed successfully to
Liferay using the Gogo shell.}
\end{figure}
\end{enumerate}
\textbf{Important:} Dev Studio's Gogo shell usage requires
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Developer
Mode} to be enabled. Developer Mode is enabled in
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} by default.
Excellent! You've learned how to manage your deployed projects with Dev
Studio's Gogo Shell integration.
\chapter{Searching Liferay DXP Source in Dev
Studio}\label{searching-liferay-dxp-source-in-dev-studio}
In Liferay Dev Studio, you can search through Liferay DXP's source code
to aid in the development of your project. Liferay provides great
resources to help with development (e.g., official documentation,
\href{https://docs.liferay.com/}{docs.liferay.com},
\href{/docs/7-2/reference/-/knowledge_base/r/sample-projects}{sample
projects}, etc.), but sometimes searching through Liferay's codebase
(i.e., platform and official apps) for patterns is just as useful. For
example, if you're creating an application that extends a class provided
in Liferay's \texttt{portal-kernel} JAR, you can inspect that class and
research how it's used in other areas of Liferay DXP's codebase.
To do this, you must be developing in a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}. Liferay Workspace is able to provide this functionality by
targeting a specific Liferay DXP version, which indexes the configured
Liferay DXP source code to provide advanced search. See the
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{Managing
the Target Platform in Liferay Workspace} tutorial for more information
on how this works.
Workspace does not perform portal source indexing by default. You must
enable this functionality by adding the following property to your
workspace's \texttt{gradle.properties} file:
\begin{verbatim}
target.platform.index.sources=true
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} Portal source indexing is disabled in Gradle workspace
version 2.0.3+ (Target Platform plugin version 2.0.0+).
\noindent\hrulefill
In this tutorial, you'll explore three use cases where advanced search
would be useful.
\begin{itemize}
\tightlist
\item
\hyperref[search-class-hierarchy]{Search class hierarchy}
\item
\hyperref[search-method-declarations]{Search declarations}
\item
\hyperref[search-annotation-references]{Search references}
\end{itemize}
These examples are just a small subset of what you can search in Liferay
Dev Studio. See Eclipse's documentation on
\href{http://help.eclipse.org/oxygen/index.jsp?topic=\%2Forg.eclipse.jdt.doc.user\%2Fconcepts\%2Fconcept-java-search.htm&resultof=\%22\%6a\%61\%76\%61\%22\%20}{Java
Search} for a comprehensive guide.
\section{Search Class Hierarchy}\label{search-class-hierarchy}
Inspecting classes that extend a similar superclass can help you find
useful patterns and examples for how you can develop your own app. For
example, suppose your app extends the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCPortlet.html}{MVCPortlet}
class. You can search classes that extend that same class in Dev Studio.
Complete the steps below for a simple example:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click the \texttt{MVCPortlet} declaration.
\item
Select \emph{Open Type Hierarchy}.
\end{enumerate}
This opens a window that lets you inspect all classes residing in the
target platform that extend \texttt{MVCPortlet}.
\begin{figure}
\centering
\includegraphics{./images/open-type-hierarchy.png}
\caption{Browse the Type Hierarchy window and open the provided classes
for examples on how to extend a class.}
\end{figure}
Great! Now you can search for all extensions and implementations of a
class/interface to aid in your quest for developing the perfect app.
\section{Search Method Declarations}\label{search-method-declarations}
Sometimes you want a search to be more granular, exploring the
declarations of a specific method provided by a class/interface. Dev
Studio's advanced search has no limits; Liferay Workspace's target
platform indexing provides method exploration too!
Suppose in the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCPortlet.html}{MVCPortlet}
class you're extending, you want to search for declarations of its
\texttt{doView} method you're overriding. Here's how to do it:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click the \texttt{doView} method declaration in your custom
app's class.
\item
Select \emph{Declarations} → \emph{Workspace}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/inspect-declared-method.png}
\caption{All declarations of the method are returned in the Search
window.}
\end{figure}
The rendered Search window displays the other occurrences in the target
platform where that method was overridden.
\section{Search Annotation
References}\label{search-annotation-references}
Annotations used in Liferay DXP's source code can sometimes be cryptic.
You can find out how they can be used in your own application by
searching for where these types of annotations exist in Liferay's target
platform.
For example, you may find some documentation on using the
\texttt{@Reference} annotation in an OSGi module and implement it in
your custom app. It could be useful to reference real world examples in
Liferay DXP's apps to check how it was used elsewhere. You can complete
this search like this:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click the \texttt{@Reference} annotation in a class.
\item
Select \emph{References} → \emph{Workspace}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/inspect-references-ide.png}
\caption{All matching annotations are displayed in the Search window.}
\end{figure}
The rendered Search window displays the other occurrences in the target
platform where that annotation was used.
Excellent! You now have the tools to search the configured target
platform specified in your Liferay Workspace!
\chapter{Debugging Liferay DXP Source in Dev
Studio}\label{debugging-liferay-dxp-source-in-dev-studio}
You can debug Liferay DXP source code in Dev Studio to help resolve
errors. Debugging Liferay DXP code follows most of the same techniques
associated with debugging in Eclipse. If you need help with general
debugging, you can visit Eclipse's documentation. Here's some helpful
Eclipse links to visit:
\begin{itemize}
\tightlist
\item
\href{http://help.eclipse.org/oxygen/index.jsp?topic=\%2Forg.eclipse.jdt.doc.user\%2Fconcepts\%2Fcdebugger.htm&cp=1_2_9}{Debugger}
\item
\href{http://help.eclipse.org/oxygen/index.jsp?topic=\%2Forg.eclipse.jdt.doc.user\%2Fconcepts\%2Fclocdbug.htm&cp=1_2_11}{Local
Debugging}
\item
\href{http://help.eclipse.org/oxygen/index.jsp?topic=\%2Forg.eclipse.jdt.doc.user\%2Fconcepts\%2Fcremdbug.htm&cp=1_2_12}{Remote
Debugging}
\end{itemize}
There are a couple Liferay-specific configurations to know before
debugging Liferay DXP code:
\begin{itemize}
\tightlist
\item
\hyperref[configure-your-target-platform]{Configure your target
platform.}
\item
\hyperref[configure-a-liferay-server-and-start-it-in-debug-mode]{Configure
a Liferay server and start it in debug mode.}
\end{itemize}
Let's explore these Liferay-specific debugging configurations.
\section{Configure Your Target
Platform}\label{configure-your-target-platform}
To configure your target platform, you must be developing in a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}. Liferay Workspace is able to provide debugging capabilities
by targeting a specific Liferay DXP version, which indexes the
configured Liferay DXP source code. Liferay Workspace does not perform
portal source indexing by default. You must enable this functionality by
adding the following property to your workspace's
\texttt{gradle.properties} file:
\begin{verbatim}
target.platform.index.sources=true
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} Portal source indexing is disabled in Gradle workspace
version 2.0.3+ (Target Platform plugin version 2.0.0+).
\noindent\hrulefill
Without specifying a target platform, Liferay DXP's source code cannot
be accessed by Dev Studio. See the
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{Managing
the Target Platform in Liferay Workspace} tutorial for more information
on how this works.
\textbf{Important:} The target platform should match the Liferay server
version you configure in the next section.
Once the target platform is configured in your workspace, Eclipse has
access to all of Liferay DXP's source code. Next, you'll configure a
Liferay server and learn how to start it in Debug mode.
\section{Configure a Liferay Server and Start It in Debug
Mode}\label{configure-a-liferay-server-and-start-it-in-debug-mode}
Configuring your target platform gives Eclipse Liferay DXP's source code
to reference. Now you must configure a Liferay server matching the
target platform version so you can deploy the custom code you wish to
debug.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Set up your Liferay DXP server to run in Dev Studio. See the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-a-liferay-server-in-dev-studio}{Installing
a Server in Dev Studio} for more details.
\item
Start the server in debug mode. To do this, click the debug button in
the Servers pane of Dev Studio.
\begin{figure}
\centering
\includegraphics{./images/ide-debug.png}
\caption{The red box in this screenshot highlights the debug button.
Click this button to start the server in debug mode.}
\end{figure}
\end{enumerate}
Awesome! You're now equipped to begin debugging in Liferay Dev Studio!
\chapter{Updating Liferay Dev Studio}\label{updating-liferay-dev-studio}
If you're already using Liferay Dev Studio but must update your
environment, follow the steps below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In Dev Studio, go to \emph{Help} → \emph{Install New
Software\ldots{}}.
\item
In the \emph{Work with} field, copy in the URL
http://releases.liferay.com/tools/ide/latest/stable/.
\item
You'll see the Dev Studio components in the list below. Check them off
and click \emph{Next}.
\begin{figure}
\centering
\includegraphics{./images/ide-updatesite-install.png}
\caption{Make sure to check all the Dev Studio components you wish to
install.}
\end{figure}
\item
Accept the terms of the agreements. Click \emph{Next}, and Dev Studio
is updated. You must restart Dev Studio for the updates to take
effect.
\end{enumerate}
You're now on the latest version of Liferay Dev Studio!
\chapter{Gradle in Dev Studio}\label{gradle-in-dev-studio}
\href{http://gradle.org/}{Gradle} is a popular open source build
automation system. You can take full advantage of Gradle in Liferay Dev
Studio through
\href{https://projects.eclipse.org/releases/photon}{Buildship}, a
collection of Eclipse plugins that provide support for building software
using Gradle with Liferay Dev Studio. Buildship is bundled with Liferay
Dev Studio versions 3.0 and higher.
\begin{figure}
\centering
\includegraphics{./images/buildship-in-liferayide.png}
\caption{Navigate to \emph{Help} → \emph{Installation Details} to view
plugins included in Dev Studio.}
\end{figure}
This reference article highlights some useful tips for leveraging Gradle
in Dev Studio.
\begin{itemize}
\tightlist
\item
\hyperref[creating-pure-gradle-projects]{Creating Pure Gradle
Projects}
\item
\hyperref[importing-pure-gradle-projects]{Importing Pure Gradle
Projects}
\item
\hyperref[gradle-tasks-and-executions]{Gradle Tasks and Executions}
\end{itemize}
Note that
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project\#liferay-dev-studio}{creating
Liferay Gradle projects} and
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project\#liferay-dev-studio}{deploying
Gradle projects} with Dev Studio are covered in their respective
articles.
The first thing you'll learn about in this tutorial is creating pure
Gradle projects in Dev Studio.
\section{Creating Pure Gradle
Projects}\label{creating-pure-gradle-projects}
Most of Dev Studio's wizards rely on your usage of Liferay Workspace.
This is for good reason; it's the recommended developer environment for
Liferay projects. You can, however, create pure Gradle projects and
manually configure them to be deployable to Liferay DXP.
You can create a pure Gradle project by using the Gradle Project wizard.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{File} → \emph{New} → \emph{Project\ldots{}}.
\item
Select \emph{Gradle} → \emph{Gradle Project}. Then click \emph{Next} →
\emph{Next}.
\item
Enter a valid project name. You can also specify your project location
and working sets.
\item
Optionally, you can navigate to the next page and specify your Gradle
distribution and other advanced options. Once you're finished, select
\emph{Finish}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/new-gradle-project.png}
\caption{You can specify your Gradle distribution and advanced options
such as home directories, JVM options, and program arguments.}
\end{figure}
Excellent! You've created a pure Gradle project using Dev Studio.
\section{Importing Pure Gradle
Projects}\label{importing-pure-gradle-projects}
You can also import existing pure Gradle projects in Dev Studio.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to \emph{File} → \emph{Import\ldots{}}.
\item
Select \emph{Gradle} → \emph{Existing Gradle Project} → \emph{Next} →
\emph{Next}.
\begin{figure}
\centering
\includegraphics{./images/import-gradle-project.png}
\caption{You can specify what Gradle project to import from the
\emph{Import Gradle Project} wizard.}
\end{figure}
\item
Click the \emph{Browse\ldots{}} button to choose a Gradle project.
\item
Optionally, you can navigate to the next page and specify your Gradle
distribution and other advanced options. Once you're finished, click
\emph{Next} again to review the import configuration. Select
\emph{Finish} once you've confirmed your Gradle project import.
\end{enumerate}
Next you'll learn about Gradle tasks and executions, and learn how to
run and display them in Dev Studio.
\section{Gradle Tasks and Executions}\label{gradle-tasks-and-executions}
Dev Studio provides two views to enhance your development experience
using Gradle: Gradle Tasks and Gradle Executions. You can open these
views by following the instructions below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to \emph{Window} → \emph{Show View} → \emph{Other\ldots{}}.
\item
Navigate to the \emph{Gradle} folder and open \emph{Gradle Tasks} and
\emph{Gradle Executions}.
\end{enumerate}
Gradle tasks and executions views open automatically once you create or
import a Gradle project.
The Gradle Tasks view lets you display the Gradle tasks available for
you to use in your Gradle project. Users can execute a task listed under
the Gradle project by double-clicking it.
\begin{figure}
\centering
\includegraphics{./images/gradle-tasks.png}
\caption{Navigate into your preferred Gradle project to view its
available Gradle tasks.}
\end{figure}
Once you've executed a Gradle task, you can open the Gradle Executions
view to inspect its output.
\begin{figure}
\centering
\includegraphics{./images/gradle-executions.png}
\caption{The Gradle Executions view helps you visualize the Gradle build
process.}
\end{figure}
Keep in mind that if you change the Gradle build scripts inside your
Gradle projects (e.g., \texttt{build.gradle} or
\texttt{settings.gradle}), you must refresh the project so Dev Studio
can account for the change and display it properly in your views. To
refresh a Gradle project, right-click on the project and select
\emph{Gradle} → \emph{Refresh Gradle Project}.
\begin{figure}
\centering
\includegraphics{./images/refresh-gradle-project.png}
\caption{Make sure to always refresh your Gradle project in Dev Studio
after build script edits.}
\end{figure}
If you prefer Eclipse refresh your Gradle projects automatically,
navigate to \emph{Window} → \emph{Preferences} → \emph{Gradle} and
enable the \emph{Automatic Project Synchronization} checkbox. If you'd
like to enable Gradle's automatic synchronization for just one Gradle
project, you can right-click a Gradle project and select
\emph{Properties} → \emph{Gradle} and enable auto sync that way.
Excellent! You're now equipped with the knowledge to add, import, and
build your Gradle projects in Liferay Dev Studio!
\chapter{Maven in Dev Studio}\label{maven-in-dev-studio}
You can take full advantage of \href{https://maven.apache.org/}{Maven}
in Liferay Dev Studio with its built-in Maven support. In this article,
you'll learn about the following topics:
\begin{itemize}
\tightlist
\item
\hyperref[installing-maven-plugins-for-dev-studio]{Installing Maven
Plugins for Liferay Dev Studio}
\item
\hyperref[importing-maven-projects]{Importing Maven Projects}
\item
\hyperref[using-the-pom-graphic-editor]{Using the POM Graphic Editor}
\end{itemize}
Note that
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project\#liferay-dev-studio}{creating}
and
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project\#liferay-dev-studio}{deploying}
Maven projects with Dev Studio are covered in their respective articles.
First you'll install the necessary Maven plugins for Dev Studio.
\section{Installing Maven Plugins for Dev
Studio}\label{installing-maven-plugins-for-dev-studio}
To support Maven projects in Dev Studio properly, you first need a
mechanism to recognize Maven projects as Dev Studio projects. For Dev
Studio to recognize the Maven project and for it to be able to leverage
Java EE tooling features (e.g., the Servers view) with the project, the
project must be a flexible web project. Dev Studio relies on the
following Eclipse plugins to provide this capability:
\begin{itemize}
\tightlist
\item
\texttt{m2e} (Maven integration for Eclipse)
\item
\texttt{m2e-wtp} (Maven integration for WTP)
\end{itemize}
All you have to do is install them so you can begin developing Maven
projects for Liferay DXP.
When first installing Dev Studio, the installation startup screen asks
if you want to install the Maven plugins automatically. Don't worry if
you missed this during setup. You'll learn how to install the required
Maven plugins for Dev Studio manually below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Help} → \emph{Install New Software}. In the
\emph{Work with} field, insert the following value:
\begin{verbatim}
http://releases.liferay.com/tools/ide/latest/stable/
\end{verbatim}
\item
Check the \emph{Liferay IDE Maven Support} option. This bundles all
the required Maven plugins you need to begin developing Maven projects
for Liferay DXP.
\begin{figure}
\centering
\includegraphics{./images/maven-install-ide-plugins.png}
\caption{You can install all the necessary Maven plugins for Dev
Studio by installing the \emph{Liferay IDE Maven Support} option.}
\end{figure}
If the \emph{Liferay IDE Maven Support} option does not appear, then
it's already installed. To verify that it's installed, uncheck the
\emph{Hide items that are already installed} checkbox and look for
\emph{Liferay IDE Maven Support} in the list of installed plugins.
Also, if you want to view everything that is bundled with the
\emph{Liferay IDE Maven Support} option, uncheck the \emph{Group items
by category} checkbox.
\item
Click \emph{Next}, review the install details, accept the term and
license agreements, and select \emph{Finish}.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} Both Maven and Eclipse have their own standard build
project lifecycles that are independent from each other. For both to
work together and run seamlessly within Dev Studio, a lifecycle mapping
is required to link both lifecycles into one combined lifecycle.
Normally, this would have to be done manually by the user. Fortunately,
the m2e-liferay plugin combines the lifecycle metadata mapping and
Eclipse build lifecycles, to provide a seamless user experience. The
lifecycle mappings for your project can be viewed by right-clicking your
project and selecting \emph{Properties} → \emph{Maven} → \emph{Lifecycle
Mapping}.
\noindent\hrulefill
Awesome! Your Dev Studio is ready to develop Maven projects for Liferay
DXP!
You'll learn about importing Maven projects in Dev Studio next.
\section{Importing Maven Projects}\label{importing-maven-projects}
To import a pre-existing, non-Liferay Maven project into Dev Studio,
follow the steps outlined below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{File} → \emph{Import} → \emph{Maven} →
\emph{Existing Maven Projects} and click \emph{Next}.
\begin{figure}
\centering
\includegraphics{./images/import-maven-project.png}
\caption{Dev Studio offers the Maven folder in the Import wizard.}
\end{figure}
\item
Click \emph{Browse\ldots{}} and select the root folder for your Maven
project. Once you've selected it, the \texttt{pom.xml} for that
project should be visible in the Projects menu.
\begin{figure}
\centering
\includegraphics{./images/select-maven-import.png}
\caption{Use the Import Maven Projects wizard to import your
pre-existing project.}
\end{figure}
\item
Click \emph{Finish}.
\end{enumerate}
Now your Maven project is available from the Package Explorer. To import
a Liferay project built with Maven, see the
\href{/docs/7-2/reference/-/knowledge_base/r/importing-projects-in-dev-studio}{Importing
Projects in Dev Studio} Next you'll learn about Dev Studio's POM
graphical editor.
\section{Using the POM Graphic
Editor}\label{using-the-pom-graphic-editor}
You're provided a graphical POM editor when opening your Maven project's
\texttt{pom.xml} in Dev Studio. This gives you several different ways to
leverage the power of Maven in your project:
\begin{itemize}
\item
\textbf{Overview:} provides a graphical interface where you can add to
and edit the \texttt{pom.xml} file.
\item
\textbf{Dependencies:} provides a graphical interface for adding and
editing dependencies in your project, as well as modifying the
\texttt{dependencyManagement} section of the \texttt{pom.xml} file.
\item
\textbf{Effective POM:} provides a read-only version of your project
POM merged with its parent POM(s), \texttt{settings.xml}, and the
settings in Eclipse for Maven.
\item
\textbf{Dependency Hierarchy:} provides a hierarchical view of project
dependencies and an interactive listing of resolved dependencies.
\item
\textbf{pom.xml:} provides an editor for your POM's source XML.
\end{itemize}
The figure below shows the \texttt{pom.xml} file editor and its modes.
\begin{figure}
\centering
\includegraphics{./images/pom-editor-features.png}
\caption{Liferay Dev Studio provides five interactive modes to help you
edit and organize your POM..}
\end{figure}
By taking advantage of these interactive modes, Dev Studio makes
modifying and organizing your POM and its dependencies a snap!
\chapter{IntelliJ}\label{intellij}
The
\href{https://plugins.jetbrains.com/plugin/10739-liferay-intellij-plugin}{Liferay
IntelliJ plugin} provides support for Liferay DXP development in
\href{https://www.jetbrains.com/idea/}{IntelliJ IDEA}. Liferay's
IntelliJ plugin provides the following built-in features:
\begin{itemize}
\tightlist
\item
Creating a Liferay Workspace (Gradle and Maven based)
\item
Creating Liferay projects (Gradle and Maven based)
\item
Liferay DXP Tomcat/Wildfly server support for project deployment and
debugging
\item
Support for adding line markers for each entity in the service editor
\item
Syntax checking, highlighting, and code completion (e.g., Bnd and XML
files)
\end{itemize}
In these articles, you'll learn how to install the Liferay IntelliJ
plugin and leverage its features to improve Liferay development with
IntelliJ IDEA.
\chapter{Installing the Liferay IntelliJ
Plugin}\label{installing-the-liferay-intellij-plugin}
There are two ways to install the Liferay IntelliJ plugin:
\begin{itemize}
\tightlist
\item
\hyperref[installing-via-intellij-marketplace]{via IntelliJ
Marketplace}
\item
\hyperref[installing-via-zip-file]{via Zip file}
\end{itemize}
Follow the steps pertaining to your preferred installation process.
\section{Installing Via IntelliJ
Marketplace}\label{installing-via-intellij-marketplace}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In IntelliJ, navigate to \emph{File} → \emph{Settings} →
\emph{Plugins}.
\item
In the Marketplace tab, search for \emph{Liferay} in the provided
search bar.
\item
Click \emph{Install} next to the Liferay IntelliJ Plugin.
\begin{figure}
\centering
\includegraphics{./images/intellij-marketplace-installation.png}
\caption{IntelliJ Marketplace offers a streamlined way to install
plugins.}
\end{figure}
\item
After the plugin has downloaded, select \emph{Restart IDE}.
\end{enumerate}
Once IntelliJ restarts, the Liferay IntelliJ plugin is installed and
ready for use.
\section{Installing Via Zip File}\label{installing-via-zip-file}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the
\href{https://plugins.jetbrains.com/plugin/10739-liferay-intellij-plugin}{JetBrains'
Liferay IntelliJ plugin} page and download it to your local machine.
\item
In IntelliJ, navigate to \emph{File} → \emph{Settings} →
\emph{Plugins}.
\item
Click the gear icon from the top menu and select \emph{Install Plugin
from Disk\ldots{}}.
\item
Select the Liferay IntelliJ plugin and click \emph{OK}.
\item
Navigate to the Installed tab in the top menu and select \emph{Restart
IDE}.
\end{enumerate}
Once IntelliJ restarts, the Liferay IntelliJ plugin is installed and
ready for use.
Great job! You're now ready to develop for Liferay DXP in IntelliJ!
\chapter{Installing a Server in
IntelliJ}\label{installing-a-server-in-intellij}
Installing a Liferay server in IntelliJ is easy. In just a few steps,
you'll have your server up and running.
\noindent\hrulefill
\textbf{Note:} Tomcat and Wildfly are the only supported Liferay app
server bundles available to install in IntelliJ.
\noindent\hrulefill
Follow these steps to install your server:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click your Liferay Workspace and select \emph{Liferay} →
\emph{InitBundle}.
This downloads the Liferay DXP bundle specified in your workspace's
\texttt{gradle.properties} file. You can change the default bundle by
updating the \texttt{liferay.workspace.bundle.url} property. For
example, this is required to update the default bundle version and/or
type (e.g., Wildfly). The downloaded bundle is stored in the
workspace's \texttt{bundles} folder.
\item
Navigate to the top right Configurations dropdown menu and select
\emph{Edit Configurations}. From here, you can configure your server's
run and debug configurations.
\begin{figure}
\centering
\includegraphics{./images/intellij-server-dropdown.png}
\caption{You have several options to choose from the server dropdown
menu.}
\end{figure}
\item
Click the \emph{Add New Configuration} button
(\includegraphics{./images/icon-intellij-add-config.png}) and select
\emph{Liferay Server}.
\item
Give your server a better name and modify any other configurations, if
necessary. Then select \emph{OK} .
\begin{figure}
\centering
\includegraphics{./images/intellij-run-debug-wizard.png}
\caption{Set your Liferay server's configurations in the Run/Debug
Configurations menu.}
\end{figure}
\end{enumerate}
Your server is now available in IntelliJ! Make sure to select it in the
Configurations dropdown before executing the configuration buttons
(below).
For reference, here's how the IntelliJ configuration buttons work with
your Liferay DXP instance:
\begin{itemize}
\tightlist
\item
\emph{Start}
(\includegraphics{./images/icon-intellij-start-server.png}): Starts
the server.
\item
\emph{Stop}
(\includegraphics{./images/icon-intellij-stop-server.png}): Stops the
server.
\item
\emph{Debug}
(\includegraphics{./images/icon-intellij-debug-server.png}): Starts
the server in debug mode. For more information on debugging in
IntelliJ, see the
\href{https://www.jetbrains.com/help/idea/debugging-code.html}{IntelliJ
Debugging} article.
\end{itemize}
Now you're ready to use your server in IntelliJ!
\chapter{Updating Liferay IntelliJ
Plugin}\label{updating-liferay-intellij-plugin}
If you're already using the Liferay IntelliJ plugin but need to update
your environment, follow the steps below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In IntelliJ, navigate to \emph{File} → \emph{Settings} →
\emph{Plugins}.
\item
Click the \emph{Updates} tab. If the Liferay IntelliJ plugin is
outdated, it will be listed as an available update.
\begin{figure}
\centering
\includegraphics{./images/update-intellij-plugin.png}
\caption{Check for updates periodically to ensure you're leveraging
the latest features.}
\end{figure}
\item
Click the \emph{Update} button (if available) to update the Liferay
IntelliJ plugin.
\item
Once it's downloaded, click the \emph{Restart IDE} button.
\begin{figure}
\centering
\includegraphics{./images/intellij-update-restart.png}
\caption{The Available Updates prompt also prints the plugin version
to which you're updating.}
\end{figure}
\end{enumerate}
Once IntelliJ restarts, the Liferay IntelliJ plugin is updated and ready
for use.
\chapter{Liferay JS Generator}\label{liferay-js-generator}
The Liferay JS Generator generates JavaScript widgets using pure
JavaScript tooling. You don't have to have a deep understanding of Java
to write a widget for Liferay DXP. See the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-and-bundling-javascript-widgets-with-javascript-tooling}{Liferay
JS Generator developer documentation} for more information on
configuring generated JavaScript widgets. This section covers these
reference topics for the Liferay JS Generator:
\begin{itemize}
\tightlist
\item
How to install the Liferay JS Generator and use it to create a JS
widget
\item
An explanation of JS Portlet Extender's method signature
\item
A reference list of the available configuration options for system
settings and instance settings
\end{itemize}
\noindent\hrulefill
\textbf{Note:} The Liferay Bundle Generator is deprecated as of v2.7.1
of the \href{https://github.com/liferay/liferay-js-toolkit}{Liferay JS
Toolkit}. It has been renamed the Liferay JS Generator. If you're still
running the Liferay Bundle Generator, we recommend that you install the
Liferay JS Generator instead at your earliest convenience, as the
Liferay Bundle Generator will be removed in future versions.
\noindent\hrulefill
The available commands for bundles created with the Liferay JS Generator
are listed below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Command
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{npm\ run\ build} & Places the output of liferay-npm-bundler in
the designated output folder. The standard output is a JAR file that can
be deployed manually to Liferay DXP. \\
\texttt{npm\ run\ deploy} & Deploys the application to the configured
server. \\
\texttt{npm\ run\ start} & Tests the application in a local webpack
installation instead of a Liferay DXP server. This speeds up development
because you can see live changes without the need to deploy. Note,
however, that because it's outside a Liferay instance, you don't have
access to Liferay's APIs. \\
\texttt{npm\ run\ translate} & Runs the translation features for your
bundle. Note that this feature requires Microsoft Translator
credentials. See
\href{/docs/7-2/frameworks/-/knowledge_base/f/using-translation-features-in-your-widget}{Using
Translation Features in Your Widget} for more information. \\
\end{longtable}
\noindent\hrulefill
\noindent\hrulefill
\textbf{Note:} By default, the webpack server uses port 8080, which
conflicts with the port used by Tomcat. You can point the webpack server
to a different port by setting the \texttt{port} key in
\texttt{.npmbuildrc}:
\begin{verbatim}
"webpack": {
"port": 2070
}
\end{verbatim}
\noindent\hrulefill
Read this section to learn how to install the Liferay JS Generator and
understand its configuration.
\chapter{Installing the JS Generator and Generating a
Bundle}\label{installing-the-js-generator-and-generating-a-bundle}
{This document has been replaced by an article on Liferay Learn and is
no longer maintained here. The Liferay CLI tool is used for creating
JavaScript application projects for Liferay versions 7.1+.}
Here you'll learn how to install the
\href{https://www.npmjs.com/package/generator-liferay-bundle}{Liferay JS
Generator} and use it to create JavaScript widgets. See the
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-an-angular-application}{Angular
Application},
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-react-application}{React
Application}, and
\href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-vue-application}{Vue
Application} articles to learn how to use your existing Angular, React,
and Vue apps in Liferay DXP.
\noindent\hrulefill
\textbf{Note:} To use the Liferay JS Generator, you must have the
Liferay JS Portlet Extender activated in your Liferay DXP instance. It's
activated by default. You can confirm this by opening the Control Menu,
navigating to the \emph{App Manager}, and searching for
\texttt{com.liferay.frontend.js.portlet.extender}.
\noindent\hrulefill
Follow these steps to create your JavaScript widget:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Install \href{http://nodejs.org/}{Node.js}. Note that Node Package
Manager (npm) is installed with this as well. You'll use npm to
install the remaining dependencies and generator, and
\href{/docs/7-2/reference/-/knowledge_base/r/setting-up-your-npm-environment}{configure
your npm environment}.
\item
Install \href{http://yeoman.io/}{Yeoman} for the generator:
\begin{verbatim}
npm install -g yeoman
\end{verbatim}
\item
Install the Liferay JS Generator:
\begin{verbatim}
npm install -g generator-liferay-js
\end{verbatim}
\item
Run the generator with the command below, select the JavaScript widget
you want to create, and answer the prompts that follow.
\begin{verbatim}
yo liferay-js
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-js-generator-prompts.png}
\caption{The liferay-js generator prompts you for widget options.}
\end{figure}
\item
If you specified your app server information when your widget was
generated, you can deploy your widget by running the command below.
You can verify this by checking the value of the \texttt{liferayDir}
entry in the widget's \texttt{.npmbuildrc}.
\begin{verbatim}
npm run deploy
\end{verbatim}
\end{enumerate}
Great! Now you know how to install and run the Liferay JS Generator.
\chapter{Understanding the JS Portlet Extender
Configuration}\label{understanding-the-js-portlet-extender-configuration}
Bundles generated with the Liferay JS Generator require specific method
signatures, MANIFEST headers, and configuration within their
\texttt{package.json} file to use the JS Portlet Extender. This
configuration is provided by default.
\section{Manifest Header}\label{manifest-header}
The OSGi bundle contains the MANIFEST header shown below, which
specifies a dependency on the JS Portlet Extender:
\begin{verbatim}
Require-Capability: osgi.extender;filter:="(osgi.extender=liferay.npm.portlet)"
\end{verbatim}
\section{Main Entry Point}\label{main-entry-point}
The main module of your JavaScript widget must export a JavaScript
function with the signature below. Bundles created with the Liferay JS
Generator have this out-of-the-box:
\begin{verbatim}
function({portletNamespace, contextPath, portletElementId, configuration}) {
...
}
\end{verbatim}
The entry point function receives one object parameter with four fields:
\begin{itemize}
\item
\texttt{portletNamespace}: the unique namespace of the widget as
defined in the Portlet specification.
\item
\texttt{contextPath}: the URL path that can be used to retrieve bundle
resources from the browser (it doesn't contain the protocol, host, or
port, just the absolute path).
\item
\texttt{portletElementId}: the DOM identifier of the widget's
\texttt{\textless{}div\textgreater{}} node that can be used to render
HTML.
\item
\texttt{configuration} (optional): since JS Portlet Extender version
1.1.0, this field contains the system (OSGi) and portlet instance
(preferences as described in the Portlet spec) configuration for the
widget. It has two subfields:
\begin{itemize}
\item
\texttt{system:} contains the system level configuration (defined in
Control Panel → System Settings)
\item
\texttt{portletInstance:} contains the per-widget configuration
(defined in the Configuration menu option of the widget)
\end{itemize}
\end{itemize}
Note that all values are received as strings, no matter what their type
is in OSGi configuration store.
The JavaScript-based widget's main \texttt{index.js} file configuration
is shown below for reference. Note that system settings and localization
are enabled in the example below:
\begin{verbatim}
export default function main({portletNamespace, contextPath, portletElementId, configuration}) {
const node = document.getElementById(portletElementId);
node.innerHTML =`
${Liferay.Language.get('porlet-namespace')}:
${portletNamespace}
${Liferay.Language.get('context-path')}:
${contextPath}
${Liferay.Language.get('portlet-element-id')}:
${portletElementId}
${Liferay.Language.get('configuration')}:
${JSON.stringify(configuration, null, 2)}
`;
}
\end{verbatim}
The JavaScript file containing the main entry point function is
specified in the \texttt{main} entry of the \texttt{package.json} file.
Below is the \texttt{main} entry for the \emph{JavaScript based widget}:
\begin{verbatim}
"main": "index.js"
\end{verbatim}
\chapter{Configuration JSON Available
Options}\label{configuration-json-available-options}
If you've
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-js-generator-and-generating-a-bundle}{created
an OSGi bundle with the Liferay JS Generator} and want to provide system
settings or instance settings for your widget, you must provide a
\texttt{configuration.json} file. This reference guide lists the
available configuration options for \texttt{configuration.json} along
with example code.
\section{JSON Format}\label{json-format}
The \texttt{configuration.json} must follow the basic pattern shown
below:
\begin{verbatim}
{
"system": {
"category": "{category identifier}",
"name": "{name of configuration}",
"fields": {
"{field id 1}": {
"type": "{field type}",
"name": "{field name}",
"description": "{field description}",
"default": "{default value}",
"options": {
"{option id 1}": "{option name 1}",
"{option id 2}": "{option name 2}",
"{option id n}": "{option name n}"
}
},
"{field id 2}": {},
"{field id n}": {}
}
},
"portletInstance": {
"name": "{name of configuration}",
"fields": {
"{field id 1}": {
"type": "{field type}",
"name": "{field name}",
"description": "{field description}",
"default": "{default value}",
"options": {
"{option id 1}": "{option name 1}",
"{option id 2}": "{option name 2}",
"{option id n}": "{option name n}"
}
},
"{field id 2}": {},
"{field id n}": {}
}
}
}
\end{verbatim}
The available options are described in the table below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Option
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Value
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{\{category\ identifier\}} & Describes the identifier of the
configuration category where the settings must be placed. It's
equivalent to the category field of the
\texttt{@ExtendedObjectClassDefinition} annotation explained
\href{/docs/7-2/frameworks/-/knowledge_base/f/categorizing-the-configuration}{here}.
The category field of \texttt{configuration.json} is optional and, when
not set, the project's name specified in \texttt{package.json} is used.
You need JS Portlet Extender 1.1.0+ for this feature to work. Otherwise,
the system configuration will show up under \emph{Platform} →
\emph{Third Party} in System Settings. \\
\texttt{\{name\ of\ configuration\}} & the configuration's name as a
string or a localization key. If no value is given, the bundler falls
back to the project's name, then description given in
\texttt{package.json}. \\
\texttt{\{field\ id\}} & the field's name as a string or a localization
key \\
\texttt{\{field\ type\}} & specifies the field's type, which can be one
of the following types: ~- \texttt{number}: an integer number~-
\texttt{float}: a floating point number~- \texttt{string}: a string~-
\texttt{boolean}: true or false~- \texttt{password}: a password
(string) \\
\texttt{\{field\ name\}} & the field's name as a string or a
localization key \\
\texttt{\{field\ description\}} & an optional string or a localization
key that describes the field's purpose and appears as hint text near
it \\
\texttt{\{default\ value\}} & an optional default value for the field \\
\texttt{options} & an optional section that defines a fixed set of
values for the field \\
\texttt{\{option\ id\}} & a string that defines the option's ID \\
\texttt{\{option\ name\}} & the option's name as a string or a
localization key \\
\end{longtable}
\noindent\hrulefill
An example configuration is shown below:
\begin{verbatim}
{
"system": {
"category": "third-party",
"name": "My project",
"fields": {
"a-number": {
"type": "number",
"name": "A number",
"description": "An integer number",
"default": "42"
},
"a-string": {
"type": "string",
"name": "A string",
"description": "An arbitrary length string",
"default": "this is a string"
},
"a-password": {
"type": "password",
"name": "A password",
"description": "A secret string",
"default": "s3.cr3t"
},
"a-boolean": {
"type": "boolean",
"name": "A boolean",
"description": "A true|false value",
"default": true
},
"an-option": {
"type": "string",
"name": "An option",
"description": "A restricted values option",
"required": true,
"default": "A",
"options": {
"A": "Option a",
"B": "Option b"
}
}
}
},
"portletInstance": {
"name": "Widget configuration",
"fields": {
"a-float": {
"type": "float",
"name": "A float",
"description": "A floating point number",
"default": "1.1"
}
}
}
}
\end{verbatim}
\chapter{Adapting Existing Apps to Run on Liferay
DXP}\label{adapting-existing-apps-to-run-on-liferay-dxp}
There are two ways to get your existing front-end applications running
on Liferay DXP:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
\href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{Migrate your
project} to a Liferay JS Toolkit project.
\item
Since v2.15.0 of the Liferay JS Toolkit, create projects normally, as
you would with
\href{https://facebook.github.io/create-react-app/}{create-react-app},
\href{https://cli.angular.io/}{Angular CLI} (any project containing
\texttt{@angular/cli} as a dependency or devDependency), and
\href{https://cli.vuejs.org/}{Vue CLI} (any project containing
\texttt{@vue/cli-service} as a dependency or devDependency), and adapt
them to run on Liferay DXP.
\end{enumerate}
Only adapt your project if you intend it to be platform-agnostic. If you
want to integrate with Liferay DXP fully and have access to all the
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-and-bundling-javascript-widgets-with-javascript-tooling}{features
and benefits} that it provides,
\href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{migrate your
project} to a true Liferay JS Toolkit project instead.
The reason for this is some of Liferay DXP's features may not be
available because the native frameworks expect certain things. For
example, Angular assumes that it controls a whole Single Page
Application as opposed to the small portion of the page that it controls
in a portlet-based platform such as Liferay DXP. Since webpack bundles
all JavaScript in a single file to consume per app, if there are five
widgets on a page, you have five copies of the framework in the
JavaScript interpreter. To prevent this,
\href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{migrate your
project} to a true Liferay JS Toolkit project instead.
To adapt your project, it must have the structure shown below:
\begin{itemize}
\item
\textbf{Angular CLI projects} must use \texttt{app-root} as the
application's Dom selector.
\item
\textbf{creact-react-app projects} must use \texttt{ReactDom.render()}
call in your entry point with a \texttt{document.getElementById()}
parameter.
\item
\textbf{Vue CLI projects} must use \texttt{\#app} as the application's
DOM selector.
\end{itemize}
When your project meets the requirements, you can follow these steps to
use the Liferay JS Generator to adapt it:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the command line and navigate to your project's folder.
\item
Run the Liferay JS Generator's \texttt{adapt} subtarget:
\begin{verbatim}
yo liferay-js:adapt
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/liferay-js-generator-adapt-run.png}
\caption{You can run the adapt subtarget of the Liferay JS Generator
to adapt your existing apps for Liferay.}
\end{figure}
\item
Answer the prompts. An example configuration appears below:
\begin{verbatim}
? Under which category should your widget be listed? category.sample
? Do you have a local installation of Liferay for development? Yes
? Where is your local installation of Liferay placed? /home/user/liferay
\end{verbatim}
Your project is adapted to use the Liferay JS Toolkit and run on
Liferay DXP!
\begin{figure}
\centering
\includegraphics{./images/liferay-js-generator-adapt-complete.png}
\caption{You can run the adapt subtarget of the Liferay JS Generator
to adapt your existing apps for Liferay.}
\end{figure}
\item
The adapt process automatically adds a few npm scripts to the
project's \texttt{package.json} so you can build and deploy your
project to Liferay DXP. Note that you can swap \texttt{npm} for
\texttt{yarn} below if you prefer to use yarn instead.
Run the command below to add a deployable JAR to the
\texttt{build.liferay} folder in your project. For example, if you
want to build the JAR and copy it to another app server, you can run
this command:
\begin{verbatim}
npm run build:liferay
\end{verbatim}
Run the command below to deploy the adapted app to your Liferay DXP
instance:
\begin{verbatim}
npm run deploy:liferay
\end{verbatim}
\end{enumerate}
Great! Now you know how to use the Liferay JS Generator to adapt your
existing apps to run on Liferay DXP. See the
\href{https://github.com/liferay/liferay-docs/tree/master/en/developer/reference/code/adapted-react-app/}{React
Guestbook App} for a working example of an adapted app.
\begin{figure}
\centering
\includegraphics{./images/liferay-js-generator-adapt-deployed.png}
\caption{Your adapted app runs in Liferay in no time.}
\end{figure}
\chapter{Liferay Workspace}\label{liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
A \emph{Liferay Workspace} is a generated environment that is built to
hold and manage your Liferay projects. This workspace is intended to aid
in the management of Liferay projects by providing various build scripts
and configured properties. You can download the
\href{https://sourceforge.net/projects/lportal/files/Liferay\%20IDE/}{Liferay
Project SDK installer} and run it to install
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI}
(default CLI for workspace), initialize a new Liferay Workspace, and
download Dev Studio DXP.
Liferay Workspace is the official way to create/manage 7.0 projects
using Gradle. Do you prefer Maven over Gradle? You can also generate a
Maven-based workspace.
You'll cover the following topics in this section:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/installing-liferay-workspace}{Installing
Liferay Workspace}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-liferay-workspace}{Creating
a Liferay Workspace}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/importing-a-liferay-workspace-into-an-ide}{Importing
a Liferay Workspace}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/setting-proxy-requirements-for-liferay-workspace}{Setting
Proxy Requirements}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/adding-a-liferay-bundle-to-liferay-workspace}{Adding
a Bundle}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/setting-environment-configurations-for-liferay-workspace}{Setting
Environment Configurations}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/building-node-js-themes-in-liferay-workspace}{Building
Node.js Themes}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/building-gradle-maven-themes-in-liferay-workspace}{Building
Gradle/Maven Themes}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{Managing
the Target Platform}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/validating-modules-against-the-target-platform}{Validating
Modules Against the Target Platform}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/leveraging-docker}{Leveraging
Docker}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/updating-liferay-workspace}{Updating
Liferay Workspace}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/updating-default-plugins-provided-by-liferay-workspace}{Updating
Default Plugins Provided by Liferay Workspace}
\end{itemize}
Liferay Workspaces can be used in many different development
environments, which makes it flexible and applicable to many different
developers. For example, a Liferay Workspace easily integrates with
Eclipse and IntelliJ, providing a seamless development experience. See
how to
\href{/docs/7-2/reference/-/knowledge_base/r/installing-liferay-workspace}{install}
and
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-liferay-workspace}{create}
a Liferay Workspace for more information.
You'll learn about workspace's anatomy and development lifecycle next.
\section{Workspace Anatomy}\label{workspace-anatomy}
A Liferay Workspace offers a development environment that can be
configured to fit your development needs. Properties are available to
help manage default and optional folders. This provides you the power to
customize your workspace's folder structure any way you'd like. The
top-level files/folder of a Liferay (Gradle) Workspace are outlined
below:
\begin{itemize}
\tightlist
\item
\texttt{bundles} (generated): the default folder for Liferay DXP
bundles.
\item
\texttt{configs}: holds the configuration files for different
environments. These files serve as your global configuration files for
all Liferay DXP servers and projects residing in your workspace. To
learn more about using the \texttt{configs} folder, see the
\hyperref[testing-projects]{Testing Projects} section.
\item
\texttt{ext} (generated): holds the Ext OSGi modules and Ext plugins.
\item
\texttt{gradle}: holds the Gradle Wrapper used by your workspace.
\item
\texttt{modules}: holds your custom modules. This can also hold
front-end portlets created with the
\href{/docs/7-2/reference/-/knowledge_base/r/js-generator}{Liferay JS
Toolkit}.
\item
\texttt{themes}: holds Node.js-style themes that use the Liferay JS
Theme Toolkit, which are built using the Liferay Theme Generator.
\item
\texttt{wars}: holds traditional WAR-style web application projects
and theme projects (i.e., generated by the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{\texttt{theme}}
project template).
\item
\texttt{build.gradle}: the common Gradle build file.
\item
\texttt{gradle.properties}: specifies the workspace's project
locations and Liferay DXP server configuration globally.
\item
\texttt{gradle-local.properties}: sets user-specific properties for
your workspace. This lets multiple users use a single workspace,
letting them configure specific properties for the workspace on their
own machine.
\item
\texttt{gradlew}: executes the Gradle command wrapper.
\item
\texttt{settings.gradle}: applies plugins to the workspace and
configures its dependencies.
\end{itemize}
If you're using a workspace generated for Maven projects, your folder
hierarchy is the same, except the Gradle build files are swapped out for
a \texttt{pom.xml} file.
Visit your workspace's \texttt{gradle.properties} file for a list of
properties (with descriptions) you can define to adapt your workspace.
For a Maven-based workspace, see the
\href{/docs/7-2/reference/-/knowledge_base/r/bundle-support-plugin}{Bundle
Support Plugin} article for info on adapting your Maven workspace.
If you'd like to keep the global Gradle properties the same, but want to
change them for yourself only (perhaps for local testing), you can
override the \texttt{gradle.properties} file with your own
\texttt{gradle-local.properties} file.
Next, you'll learn about workspace's development lifecycle.
\section{Development Lifecycle}\label{development-lifecycle}
Liferay Workspaces offer a full development lifecycle for your projects
to make your Liferay development easier than ever. The development
lifecycle includes
\begin{itemize}
\item
\hyperref[creating-projects]{Creating projects}
\item
\hyperref[building-projects]{Building projects}
\item
\hyperref[deploying-projects]{Deploying projects}
\item
\hyperref[testing-projects]{Testing projects}
\item
\hyperref[releasing-projects]{Releasing projects}
\item
\hyperref[development-lifecycle]{Test}
\end{itemize}
You'll learn about each lifecycle option next.
\section{Creating Projects}\label{creating-projects}
Workspace provides a slew of
\href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{project
templates} that you can use to create many different types of Liferay
projects. Workspace also provides development support for front-end
portlets generated with the
\href{/docs/7-2/reference/-/knowledge_base/r/js-generator}{Liferay JS
Toolkit}. They're stored in the \texttt{modules} folder by default.
You can also configure where to generate certain projects (modules,
themes, WARs, etc.). These settings are documented in the
\texttt{gradle.properties} file. See the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Creating
a Project} article for more information.
Liferay Workspace manages theme projects in two separate folders based
on how they're created:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Liferay
Theme Generator} (Node.js-based themes that use the Liferay JS Theme
Toolkit)
\item
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{Project
template/archetype} (Gradle/Maven-based themes)
\end{itemize}
Liferay Workspace offers an environment where developers can use the
Liferay Theme Generator to create themes and their work can be
seamlessly integrated into their overall DevOps strategy. You can
leverage the Liferay Theme Generator to
\href{/docs/7-2/reference/-/knowledge_base/r/building-node-js-themes-in-liferay-workspace}{create
Node.js-based themes inside workspace} or you can leverage it externally
and copy themes into Workspace.
Workspace also offers a
\href{/docs/7-2/reference/-/knowledge_base/r/building-gradle-maven-themes-in-liferay-workspace}{traditional
Java-based theme approach} (leveraging Gradle/Maven) for those that
can't use the Liferay JS Theme Toolkit's tools in their CI environment.
\section{Building Projects}\label{building-projects}
Liferay Workspace abstracts many build requirements away so you can
focus on developing projects instead of worrying about how to build
them. This is done by incorporating a slew of plugins under the hood to
allow for easily accessible tooling. See the
\href{/docs/7-2/reference/-/knowledge_base/r/gradle-plugins}{Gradle
Plugins} and
\href{/docs/7-2/reference/-/knowledge_base/r/maven-plugins}{Maven
Plugins} sections for information on some of the plugins provided by
workspace.
Gradle-based workspaces also include a Gradle wrapper in its ROOT folder
(e.g., \texttt{gradlew}), which you can leverage to execute Gradle
commands. This means that you can run familiar Gradle build commands
(e.g., \texttt{build}, \texttt{clean}, \texttt{compile}, etc.) from a
Liferay Workspace without having Gradle installed on your machine. For
Maven-based workspaces, Maven build commands are supported (e.g.,
\texttt{package}, \texttt{verify}, \texttt{deploy}, etc.).
Liferay Workspace lets you build your projects out-of-the-box without
the hassle of manual build configurations.
\section{Deploying Projects}\label{deploying-projects}
Liferay Workspace provides easy-to-use deployment mechanisms that let
you deploy your project to a Liferay server without any custom
configuration. To learn more about deploying projects from a workspace,
visit the
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploying
a Project} article.
\section{Testing Projects}\label{testing-projects}
Liferay provides many configuration settings for 7.0. Configuring
several different Liferay DXP installations to simulate/test certain
behaviors can become cumbersome and time consuming. With Liferay
Workspace, you can easily organize environment settings and generate an
environment installation with those settings.
Liferay Workspace provides the \texttt{configs} folder, which lets you
configure different environments in the same workspace. For example, you
could configure separate Liferay DXP environment settings for
development, testing, and production in a single Liferay Workspace. So
how does it work?
The \texttt{configs} folder offers six subfolders:
\texttt{common}: holds a common configuration that you want applied to
all environments.
\texttt{dev}: holds the development configuration.
\texttt{docker}: holds the configuration for a Docker container.
\texttt{local}: holds the configuration intended for testing locally.
\texttt{prod}: holds the configuration for a production site.
\texttt{uat}: holds the configuration for a UAT site.
You're not limited to just these environments. You can create any
subfolder in the \texttt{configs} folder (e.g., \texttt{aws},
\texttt{test}, etc.) to simulate any environment. Each environment
folder can supply its own database, \texttt{portal-ext.properties},
Elasticsearch, etc. The files in each folder overlay your Liferay DXP
installation, which you generate from within workspace.
\begin{figure}
\centering
\includegraphics{./images/workspace-configs.png}
\caption{The \texttt{configs/common} and
\texttt{configs/{[}environment{]}} overlay you Liferay DXP bundle when
it's generated.}
\end{figure}
When workspace generates a Liferay DXP bundle, these things happen:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Configuration files found in the \texttt{configs/common} folder are
applied to the Liferay DXP bundle.
\item
The configured workspace environment (\texttt{dev}, \texttt{local},
etc.) is applied on top of any existing configurations from the
\texttt{common} folder.
\end{enumerate}
See the
\href{/docs/7-2/reference/-/knowledge_base/r/setting-environment-configurations-for-liferay-workspace}{Setting
Environment Configurations for Liferay Workspace} article for more
information.
\section{Releasing Projects}\label{releasing-projects}
Liferay Workspace does not provide a built-in release mechanism, but
there are easy ways to use external release tools with workspace. The
most popular choice is uploading your projects to a Maven Nexus
repository. You could also use other release tools like
\href{https://www.jfrog.com/artifactory/}{Artifactory}.
Uploading projects to a remote repository is useful if you need to share
them with other non-workspace projects. Also, if you're ready for your
projects to be in the spotlight, uploading them to a public remote
repository gives other developers the chance to use them.
For more instructions on how to set up a Maven Nexus repository for your
workspace's projects, see the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-maven-repository}{Creating
a Maven Repository} and
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-liferay-maven-artifacts-to-a-repository}{Deploying
Liferay Maven Artifacts to a Repository} articles.
\chapter{Installing Liferay
Workspace}\label{installing-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
You can install Liferay Workspace using the Liferay Project SDK
installer. This installs JPM and
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} into
your user home folder and optionally initializes a Liferay Workspace
folder. This is the same installer covered in the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-blade-cli}{Installing
Blade CLI} article.
Follow the steps below to download and install Liferay Workspace:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Download the latest
\href{https://sourceforge.net/projects/lportal/files/Liferay\%20IDE/}{Liferay
Project SDK installer} that corresponds with your operating system
(e.g., Windows, MacOS, or Linux). The Project SDK installer is listed
under \emph{Liferay IDE}, so the folder versions are based on IDE
releases. You can select an installer that does not include Dev Studio
DXP, if you don't intend to use it. The Project SDK installer is
available for versions 3.2.0+. Do \textbf{not} select the large green
download button; this downloads Liferay Portal instead.
\item
Run the installer. Click \emph{Next} to step through the installer's
introduction.
\item
Set the folder where your Liferay Workspace should be initialized.
\begin{figure}
\centering
\includegraphics{./images/blade-installer-workspace-init.png}
\caption{Determine where your Liferay Workspace should reside.}
\end{figure}
Then click \emph{Next}.
\item
Choose the Liferay product type you intend to use with the workspace.
Then click \emph{Next}.
\begin{figure}
\centering
\includegraphics{./images/installer-workspace-type.png}
\caption{Select the product version you'll use with your Liferay
Workspace.}
\end{figure}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You'll be prompted for your liferay.com username and password
before downloading the Liferay DXP bundle. Your credentials are not saved
locally; they're saved as a token in the `~/.liferay` folder. The token is
used by your workspace if you ever decide to redownload a DXP bundle.
Furthermore, the bundle that is downloaded in your workspace is also
copied to your `~/.liferay/bundles` folder, so if you decide to initialize
another Liferay DXP instance of the same version, the bundle is not
re-downloaded. See the
[Adding a Liferay Bundle to Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/adding-a-liferay-bundle-to-liferay-workspace)
for more information on this topic.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
Click \emph{Next} to begin installing Liferay Workspace on your
machine.
\end{enumerate}
That's it! Liferay Workspace is now installed on your machine!
\chapter{Creating a Liferay
Workspace}\label{creating-a-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
You can create a Liferay Workspace using the following tools:
\begin{itemize}
\tightlist
\item
\hyperref[blade-cli]{Blade CLI}
\item
\hyperref[dev-studio]{Dev Studio}
\item
\hyperref[intellij]{IntelliJ}
\item
\hyperref[maven]{Maven}
\end{itemize}
Visit the appropriate section to learn how to create a workspace with
the highlighted tool.
\section{Blade CLI}\label{blade-cli-3}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to the folder where you want your workspace generated.
\item
Run the following command to build a Gradle-based workspace:
\begin{verbatim}
blade init -v 7.2 [WORKSPACE_NAME]
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note**: The version you set when first initializing your workspace is
stored in the workspace's `.blade.properties` file with the
`liferay.version.default` property. This version is applied when creating
projects based on the corresponding project template versions.
If you wish to develop projects for a different Liferay DXP version, you can
pass a different version in the Blade init command. For example,
```bash
blade init -v 7.0 [WORKSPACE_NAME]
```
\end{verbatim}
\noindent\hrulefill
You can also create a Maven-based workspace with Blade CLI. See the
\hyperref[maven]{Maven} section for more information.
\section{Dev Studio}\label{dev-studio}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select \emph{File} → \emph{New} → \emph{Liferay Workspace Project}.
\begin{figure}
\centering
\includegraphics{./images/selecting-liferay-workspace.png}
\caption{By selecting \emph{Liferay Workspace Project}, you begin the
process of creating a new workspace for your Liferay projects.}
\end{figure}
A New Liferay Workspace dialog appears, presenting several
configuration options.
\item
Give your workspace project a name.
\item
Choose the location where you'd like your workspace to reside.
Checking the \emph{Use default location} checkbox places your Liferay
Workspace in the Eclipse workspace you're working in.
\item
Select the build tool you want your workspace to be built with (i.e.,
Gradle or Maven).
\item
Choose the Liferay Portal version you plan to develop for (i.e., 7.2,
7.1, or 7.0).
\item
Select the specific target platform version corresponding to the GA
release you're developing for (e.g., 7.2.0 → 7.2 GA1). For more
information on target platform benefits, see the
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{Managing
the Target Platform} articles.
\item
Check the \emph{Download Liferay bundle} checkbox if you'd like to
auto-generate a Liferay instance in your workspace. You'll be prompted
to name the server and provide the server's download URL, if selected.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can configure a pre-existing Liferay bundle in your
workspace by creating a folder for the bundle in your workspace and
configuring it in the workspace's `gradle.properties` file by setting the
`liferay.workspace.home.dir` property.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\item
Check the \emph{Add project to working set} checkbox if you want your
workspace to be a part of a larger working set you've already created
in Dev Studio. For more information on working sets, visit
\href{https://help.eclipse.org/mars/index.jsp?topic=\%2Forg.eclipse.platform.doc.user\%2Fconcepts\%2Fcworkset.htm}{Eclipse
Help}.
\item
Click \emph{Finish} to create your Liferay Workspace.
\begin{figure}
\centering
\includegraphics{./images/new-workspace-menu.png}
\caption{Dev Studio provides an easy-to-follow menu to create your
Liferay Workspace.}
\end{figure}
\end{enumerate}
A dialog appears prompting you to open the Liferay Workspace
perspective. Click \emph{Yes}, and your perspective will switch to
Liferay Workspace.
\section{IntelliJ}\label{intellij-1}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the New Project wizard by selecting \emph{File} → \emph{New} →
\emph{Project}. If you're starting IntelliJ for the first time, you
can do this by selecting \emph{Create New Project} in the opening
window.
\item
Select \emph{Liferay} from the left menu.
\item
Choose the build type for your workspace (i.e., Gradle or Maven). Then
click \emph{Next}.
\begin{figure}
\centering
\includegraphics{./images/intellij-workspace-build.png}
\caption{Choose \emph{Liferay Gradle Workspace} or \emph{Liferay Maven
Workspace}, depending on the build you prefer.}
\end{figure}
\item
Specify your workspace's name, location, intended Liferay DXP version,
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{target
platform}, and SDK (i.e., Java JDK). Then click \emph{Finish}.
\begin{figure}
\centering
\includegraphics{./images/intellij-workspace-project.png}
\caption{Specify your workspace's configurations.}
\end{figure}
\item
A window opens for additional build configurations for the build type
you selected (i.e., Gradle or Maven). Verify the settings and click
\emph{OK}.
\end{enumerate}
\section{Maven}\label{maven-2}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Execute the following Maven command:
\begin{verbatim}
mvn archetype:generate -Dfilter=liferay
\end{verbatim}
\item
Select the \texttt{com.liferay.project.templates.workspace} archetype
to generate.
\item
Step through the remaining prompts to generate the workspace project.
\end{enumerate}
A Maven-based Liferay Workspace can also be generated using Blade CLI.
Follow \hyperref[blade-cli]{Blade CLI's} workspace creation instructions
and insert the \texttt{-b\ \ maven} parameter in the Blade command.
\chapter{Importing a Liferay Workspace into an
IDE}\label{importing-a-liferay-workspace-into-an-ide}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay supports two IDEs with preconfigured Liferay Workspace wizards
and functionalities
\begin{itemize}
\tightlist
\item
\hyperref[dev-studio]{Dev Studio}
\item
\hyperref[intellij]{IntelliJ}
\end{itemize}
These aren't the only IDEs you can leverage to use Liferay Workspace,
but they are the ones with out-of-the-box support for it.
Visit the appropriate section to learn how to import a workspace with
the highlighted tool.
\section{Dev Studio}\label{dev-studio-1}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{File} → \emph{Import} → \emph{Liferay} →
\emph{Liferay Workspace Project}.
\item
Click \emph{Next} and browse for your workspace project.
\begin{figure}
\centering
\includegraphics{./images/liferay-workspace-import.png}
\caption{You can import an existing Liferay Workspace into your
current Dev Studio session.}
\end{figure}
\item
Once you've selected you workspace, click \emph{Finish}.
\end{enumerate}
\section{IntelliJ}\label{intellij-2}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select \emph{File} → \emph{New} → \emph{Project from Existing
Sources\ldots{}}.
\item
Select the workspace you want to import. Then click \emph{OK}.
\begin{figure}
\centering
\includegraphics{./images/intellij-import-workspace.png}
\caption{Specify your workspace's configurations.}
\end{figure}
\item
Click the \emph{Import project from external model} radio button and
select the build tool your workspace uses (e.g., Gradle or Maven).
\item
Configure the project import (if necessary) and then click
\emph{Finish}. See the
\href{https://www.jetbrains.com/help/idea/creating-and-managing-projects.html\#importing-project}{Import
a Project} section of IntelliJ's official documentation for more
information.
\item
Step through the remaining import prompts and then open your imported
workspace as you desire (i.e., in the current window or a new window).
\end{enumerate}
\chapter{Setting Proxy Requirements for Liferay
Workspace}\label{setting-proxy-requirements-for-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
If you're working behind a corporate firewall that requires using a
proxy server to access external repositories, you need to add some extra
configuration to make Liferay Workspace work within your environment.
You'll learn how to set proxy requirements for both Gradle and Maven
environments.
\section{Gradle}\label{gradle-1}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your \texttt{\textasciitilde{}/.gradle/gradle.properties} file.
Create this file if it does not exist.
\item
Add the following properties to the file:
\begin{verbatim}
systemProp.http.proxyHost=www.somehost.com
systemProp.http.proxyPort=1080
systemProp.https.proxyHost=www.somehost.com
systemProp.https.proxyPort=1080
\end{verbatim}
Make sure to replace the proxy host and port values with your own.
\item
If the proxy server requires authentication, also add the following
properties:
\begin{verbatim}
systemProp.http.proxyUser=userId
systemProp.http.proxyPassword=yourPassword
systemProp.https.proxyUser=userId
systemProp.https.proxyPassword=yourPassword
\end{verbatim}
\end{enumerate}
Excellent! Your proxy settings are set in your Liferay Workspace's
Gradle environment.
\section{Maven}\label{maven-3}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your \texttt{\textasciitilde{}/.m2/settings.xml} file. Create
this file if it does not exist.
\item
Add the following XML snippet to the file:
\begin{verbatim}
httpProxy
true
http
www.somehost.com
1080
httpsProxy
true
https
www.somehost.com
1080
\end{verbatim}
Make sure to replace the proxy host and port values with your own.
\item
If the proxy server requires authentication, also add the
\texttt{username} and \texttt{password} proxy properties. For example,
the HTTP proxy authentication configuration would look like this:
\begin{verbatim}
httpProxy
true
http
www.somehost.com
1080
userID
somePassword
\end{verbatim}
\end{enumerate}
Excellent! Your Maven proxy settings are now set.
\chapter{Adding a Liferay Bundle to Liferay
Workspace}\label{adding-a-liferay-bundle-to-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay Workspaces can generate and hold a Liferay Server. This lets you
build/test your workspace's plugins against a running Liferay instance.
Follow the instructions below to get started.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your workspace's root \texttt{gradle.properties} file.
\item
Set the \texttt{liferay.workspace.bundle.url} property to the bundle's
download URL you want to generate and install. For example,
\begin{verbatim}
liferay.workspace.bundle.url=https://releases-cdn.liferay.com/portal/7.2.0-ga1/liferay-ce-portal-tomcat-7.2.0-ga1-20190531153709761.7z
\end{verbatim}
For DXP subscribers, it would look like this:
\begin{verbatim}
liferay.workspace.bundle.url=https://api.liferay.com/downloads/portal/7.2.10/liferay-dxp-tomcat-7.2.10-ga1-20190531140450482.7z
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** The DXP download URL must be set to the bundle hosted on
*api.liferay.com*. It can be tricky to find the fully qualified bundle
name/number for the DXP bundle you want. You cannot access Liferay's API
site directly to find it, so you must start to download DXP manually from
Liferay's Customer Portal, take note of the file name, and append it to
`https://api.liferay.com/downloads/portal/`.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
DXP subscribers must also set the `liferay.workspace.bundle.token.download`
property to `true` to allow your workspace to access Liferay's API site.
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\item
Navigate to your workspace's root folder and run
\begin{verbatim}
blade server init
\end{verbatim}
\item
Verify your bundle was downloaded. The bundle is generated in the
\texttt{bundles} folder by default. You can change this by setting the
\texttt{gradle.properties} file's \texttt{liferay.workspace.home.dir}
property to a different folder.
\end{enumerate}
You can also produce a distributable Liferay bundle (Zip or Tar) from
within a workspace. To do this, navigate to your workspace's root folder
and run the following command:
\begin{verbatim}
./gradlew distBundle[Zip|Tar]
\end{verbatim}
Your distribution file is available from the workspace's \texttt{/build}
folder.
\noindent\hrulefill
\textbf{Note:} You can define different environments for your Liferay
bundle for easy testing. You can learn more about this in the
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace\#testing-projects}{Testing
Projects} section.
\noindent\hrulefill
You're all set to develop projects for a nested Liferay DXP bundle.
\chapter{Setting Environment Configurations for Liferay
Workspace}\label{setting-environment-configurations-for-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay Workspace offers the \texttt{configs} folder, which provides a
way to organize multiple environment settings and generate a Liferay
bundle for each environment configuration.
To simulate using the \texttt{configs} folder, you'll explore a typical
scenario. Suppose you want a local Liferay DXP installation for testing
and a UAT installation for simulating a production site. Assume you want
the following configuration for the two environments:
\textbf{Local Environment}
\begin{itemize}
\tightlist
\item
Use MySQL database pointing to localhost
\item
Skip setup wizard
\end{itemize}
\textbf{UAT Environment}
\begin{itemize}
\tightlist
\item
Use MySQL database pointing to a live server
\item
Skip setup wizard
\end{itemize}
To configure these two environments in your workspace, follow the steps
below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the \texttt{configs/common} folder and add the
\texttt{portal-setup-wizard.properties} file with the
\texttt{setup.wizard.enabled=false} property.
\item
Open the \texttt{configs/local} folder and configure the MySQL
database settings for localhost in a \texttt{portal-ext.properties}
file.
\item
Open the \texttt{configs/uat} folder and configure the MySQL database
settings for the live server in a \texttt{portal-ext.properties} file.
\item
Now that your two environments are configured, generate one of them:
\begin{verbatim}
blade server init --environment uat
\end{verbatim}
\item
To generate a distributable Liferay DXP installation of the
environment to the workspace's \texttt{/build} folder, run
\begin{verbatim}
./gradlew distBundle[Zip|Tar] -Pliferay.workspace.environment=uat
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You may prefer to set your workspace environment in the
`gradle.properties` file instead of passing it via Gradle command. If so,
it's recommended to set the workspace environment variable inside the
`[USER_HOME]/.gradle/gradle.properties` file.
```properties
liferay.workspace.environment=local
```
The variable is set to `local` by default.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
You've successfully configured two environments and generated one of them.
\end{verbatim}
Awesome! You can now test various Liferay DXP bundle environments using
Liferay Workspace.
\chapter{Building Node.js Themes in Liferay
Workspace}\label{building-node.js-themes-in-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay Workspace reserves the \texttt{themes} folder only for themes
that are created with the Themes Generator. There are no Blade
CLI-provided commands or Maven archetypes to generate a theme for this
folder. You must leverage the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{Liferay
Theme Generator} from within the \texttt{themes} folder to create them;
you can also copy a generated theme into the folder.
You'll demo this theme management capability next. Be sure the Liferay
Theme Generator's required tooling is installed.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your workspace's \texttt{themes} folder and run the
following command:
\begin{verbatim}
yo liferay-theme
\end{verbatim}
Follow the prompts to create your theme.
\item
Navigate into your new theme and run
\begin{verbatim}
./gradlew build
\end{verbatim}
Liferay Workspace builds the front-end theme using Gradle. Under the
hood, Liferay's
\href{/docs/7-2/reference/-/knowledge_base/r/node-gradle-plugin}{Node
Gradle Plugin} is applied and used to build your theme.
\item
Workspace is smart enough to differentiate between theme types. For
instance, you can't copy a theme built with the Theme Generator into
the \texttt{wars} folder and expect it to build. You can test if your
project is recognized by workspace by running this command from
workspace's root folder:
\begin{verbatim}
./gradlew projects
\end{verbatim}
Your CLI should display your new theme under the \texttt{themes}
project.
```bash Root project `liferay-workspace' +--- Project `:themes'
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
\--- Project ':themes:my-generated-theme'
\end{verbatim}
\noindent\hrulefill ```
\begin{verbatim}
If you moved a WAR-style theme (Gradle/Maven-based) into the `themes`
folder, it is not recognized by the Gradle `projects` command.
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} Workspace identifies whether a theme was generated by the
Theme Generator by checking whether it has a \texttt{package.json} file.
Any theme without this file is not compatible in the \texttt{themes}
folder.
\noindent\hrulefill
Excellent! You learned how generated themes are recognized in workspace
and where they should reside. For more information on building
Gradle/Maven-based themes in workspace, see its dedicated
\href{/docs/7-2/reference/-/knowledge_base/r/building-gradle-maven-themes-in-liferay-workspace}{article}.
\chapter{Building Gradle/Maven Themes in Liferay
Workspace}\label{building-gradlemaven-themes-in-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay Workspace provides the \texttt{wars} folder for any WAR-style
project. Themes created with
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} or
Maven using the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{\texttt{theme}}
project template or archetype are automatically generated here when
creating the project within Workspace.
Follow the steps below to build a Gradle/Maven theme in workspace's
\texttt{wars} folder:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Follow the
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Creating
a Project} article to generate a project based on a project template
or archetype. Make sure to select the \texttt{theme} template.
Themes built using Liferay's \texttt{theme} project template are
always WARs and should always reside in Workspace's \texttt{wars}
folder. They should never be moved to the \texttt{themes} folder; that
folder is reserved for
\href{/docs/7-2/reference/-/knowledge_base/r/building-node-js-themes-in-liferay-workspace}{themes
generated by the Theme Generator}.
\item
Navigate into your new theme and run
\begin{verbatim}
./gradlew build
\end{verbatim}
Liferay Workspace builds the theme using Gradle. Under the hood,
Liferay's
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-gradle-plugin}{Theme
Builder Gradle Plugin} is applied and used to build your theme. It
works similarly in a Maven workspace. See the
\href{/docs/7-1/frameworks/-/knowledge_base/frameworks/building-themes-in-a-maven-project}{Building
Themes in a Maven Project} article for more information.
\end{enumerate}
Awesome! You know how WAR-style themes are built in workspace and where
they should reside.
\chapter{Managing the Target
Platform}\label{managing-the-target-platform}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
\noindent\hrulefill
\textbf{Note:} The Target Platform articles currently assume you're
using Gradle as a build tool. If your projects are built with Maven, you
can still leverage the Target Platform features, but it is not built
into Liferay Workspace \emph{yet}
(\href{https://issues.liferay.com/browse/LPS-90524}{LPS-90524}). See the
\href{/docs/7-2/reference/-/knowledge_base/r/targeting-a-platform-with-maven}{Targeting
a Platform with Maven} article to set the Target Platform for
Maven-based projects.
\noindent\hrulefill
Liferay Workspace helps you target a specific release of Liferay DXP, so
dependencies get resolved properly. This makes upgrades easy: specify
your target platform, and Workspace points to the new version. All your
dependencies are updated to the latest ones provided in the targeted
release.
\noindent\hrulefill
\textbf{Note:} There are times when configuring dependencies based on a
version range is better than tracking exact versions. See the
\href{/docs/7-2/customization/-/knowledge_base/c/semantic-versioning}{Semantic
Versioning} tutorial for more details.
\noindent\hrulefill
Next, you'll discover how all of this is possible.
\section{Dependency Management with
BOMs}\label{dependency-management-with-boms}
You can target a version by importing a predefined bill of materials
(BOM). This only requires that you specify a property in your
workspace's \texttt{gradle.properties} file (see
\href{/docs/7-2/reference/-/knowledge_base/r/setting-the-target-platform}{this
article} for details).
\noindent\hrulefill
\textbf{Note:} The Target Platform feature is only supported for Gradle
projects at this time.
\noindent\hrulefill
Each Liferay DXP version has a predefined BOM that you can specify for
your workspace to reference. Each BOM defines the artifacts and their
versions used in the specific release. BOMs list all dependencies in a
management fashion, so it doesn't \textbf{add} dependencies to your
project; it only \textbf{provides} your build tool (e.g., Gradle or
Maven) the versions needed for the project's defined artifacts. This
means you don't need to specify your dependency versions; the BOM
automatically defines the appropriate artifact versions based on the
BOM.
You can override a BOM's defined artifact version by specifying a
different version in your project's \texttt{build.gradle}. Artifact
versions defined in your project's build files override those specified
in the predefined BOM. Note that overriding the BOM can be dangerous;
make sure the new version is compatible in the targeted platform.
For more information on BOMs, see the
\href{https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism\#Importing_Dependencies}{Importing
Dependencies} section in Maven's official documentation. To view a BOM
file and its mapping of artifacts and versions, visit
\href{https://repository.liferay.com}{repository.liferay.com} and search
for the BOM artifacts (e.g.,
\href{https://repository.liferay.com/nexus/index.html\#nexus-search;quick~release.portal.bom}{release.portal.bom}
and
\href{https://repository.liferay.com/nexus/index.html\#nexus-search;quick~release.dxp.bom}{release.dxp.bom}).
Pretty cool, right? Next, you'll learn how to leverage platform
targeting in Dev Studio.
\section{Leveraging Target Platform in Dev
Studio}\label{leveraging-target-platform-in-dev-studio}
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio 3.2+} helps you streamline targeting a specific version even
more. Dev Studio can index the configured Liferay DXP source code to
\begin{itemize}
\tightlist
\item
provide advanced Java search (Open Type and Reference Searching)
(\href{/docs/7-2/reference/-/knowledge_base/r/searching-product-source-in-dev-studio}{article})
\item
debug Liferay DXP sources
(\href{/docs/7-2/reference/-/knowledge_base/r/debugging-product-source-in-dev-studio}{article})
\end{itemize}
To enable this functionality, set the following property in your
workspace's \texttt{gradle.properties} file:
\begin{verbatim}
target.platform.index.sources=true
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} Portal source indexing is disabled in Gradle workspace
version 2.0.3+ (Target Platform plugin version 2.0.0+). See the
\href{/docs/7-2/reference/-/knowledge_base/r/updating-liferay-workspace}{Updating
Liferay Workspace} article for instructions on how to update your
workspace.
\noindent\hrulefill
These options in Dev Studio are only available when developing in a
Liferay Workspace, or if you have the
\href{/docs/7-2/reference/-/knowledge_base/r/target-platform-gradle-plugin}{Target
Platform Gradle plugin} applied to your multi-module Gradle project with
specific configurations. See the
\href{/docs/7-2/reference/-/knowledge_base/r/targeting-a-platform-outside-of-workspace}{Targeting
a Platform Outside of Workspace} article for more info on applying the
Target Platform Gradle plugin.
Continue on to learn how to set the target platform.
\chapter{Setting the Target Platform}\label{setting-the-target-platform}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Setting the target platform version to develop for takes two steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the workspace's \texttt{gradle.properties} file and set the
\texttt{liferay.workspace.target.platform.version} property to the
version you want to target. For example,
\begin{verbatim}
liferay.workspace.target.platform.version=7.2.0
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You must explicitly uncomment the property in your workspace's
`gradle.properties` file to set it. Target Platform is not enabled by
default.
\end{verbatim}
\noindent\hrulefill
\begin{verbatim}
If you're using Liferay DXP, you can set the property like this:
```properties
liferay.workspace.target.platform.version=7.2.10
```
The versions following a GA1 release of DXP follow fix pack versions (e.g.,
`7.2.10.fp1`, `7.2.10.fp2`, etc.).
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Once the target platform is configured, check to make sure no
dependencies in your Gradle build files specify a version. The
versions are now imported from the configured target platform's BOM.
For example, a simple MVC portlet's \texttt{build.gradle} may look
something like this:
\begin{verbatim}
dependencies {
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel"
compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib"
compileOnly group: "javax.portlet", name: "portlet-api"
compileOnly group: "javax.servlet", name: "javax.servlet-api"
compileOnly group: "jstl", name: "jstl"
compileOnly group: "org.osgi", name: "osgi.cmpn"
}
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\textbf{Note}: The \texttt{liferay.workspace.target.platform.version}
property also sets the distro JAR, which can be used to validate your
projects during the build process. See the
\href{/docs/7-2/reference/-/knowledge_base/r/validating-modules-against-the-target-platform}{Validating
Modules Against the Target Platform} articles for more info.
\noindent\hrulefill
\noindent\hrulefill
\textbf{Note:} The target platform functionality is available in Liferay
Workspace version 1.9.0+. If you have an older version, you must update
it to leverage platform targeting. See the
\href{/docs/7-2/reference/-/knowledge_base/r/updating-liferay-workspace}{Updating
Liferay Workspace} article to do this.
\noindent\hrulefill
You've configured your target platform in workspace. You're all set!
\chapter{Targeting a Platform Outside of
Workspace}\label{targeting-a-platform-outside-of-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
If you prefer to not use Liferay Workspace, but still want to target a
platform, you must apply the
\href{/docs/7-2/reference/-/knowledge_base/r/target-platform-gradle-plugin}{Target
Platform Gradle plugin} to the root \texttt{build.gradle} file of your
custom multi-module Gradle build.
To do this, follow the steps below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your project's \texttt{build.gradle} file and add this:
\begin{verbatim}
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.target.platform", version: "2.0.0"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
\end{verbatim}
This sets the dependency on the Target Platform Gradle plugin and
configures the repository that provides the necessary artifacts for
your project build.
\item
Apply Liferay's Target Platform Gradle plugin to the
\texttt{build.xml} file:
\begin{verbatim}
apply plugin: "com.liferay.target.platform"
\end{verbatim}
\item
Set the Target Platform plugin's dependencies:
\begin{verbatim}
dependencies {
targetPlatformBoms group: "com.liferay.portal", name: "release.portal.bom", version: "7.2.0"
targetPlatformBoms group: "com.liferay.portal", name: "release.portal.bom.compile.only", version: "7.2.0"
targetPlatformBoms group: "com.liferay.portal", name: "release.portal.bom.third.party", version: "7.2.0"
}
\end{verbatim}
These dependencies are described below:
\texttt{com.liferay.ce.portal.bom}: provides all the artifacts
included in Liferay DXP.
\texttt{com.liferay.ce.portal.compile.only}: provides artifacts that
are not included in Liferay DXP, but are necessary to reference during
the build (e.g., \texttt{org.osgi.core}).
\texttt{release.portal.bom.third.party}: provides all third party
artifacts that make up the Liferay Portal bundle.
Liferay DXP users must replace the artifact names and versions:
\begin{itemize}
\tightlist
\item
\texttt{release.portal.bom} → \texttt{release.dxp.bom}
\item
\texttt{release.portal.bom.compile.only} →
\texttt{release.dxp.bom.compile.only}
\item
\texttt{release.portal.bom.third.party} →
\texttt{release.dxp.bom.third.party}
\item
\texttt{7.2.0} → \texttt{7.2.10}
\end{itemize}
\item
If you're interested in
\href{/docs/7-2/reference/-/knowledge_base/r/searching-product-source-in-dev-studio}{advanced
search} and/or
\href{/docs/7-2/reference/-/knowledge_base/r/debugging-product-source-in-dev-studio}{debugging}
Liferay DXP's source using
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio}, you must also apply the following configuration:
\begin{verbatim}
targetPlatformIDE {
includeGroups "com.liferay", "com.liferay.portal"
}
\end{verbatim}
This indexes the target platform's source code and makes it available
to Dev Studio.
\end{enumerate}
Now you can define your target platform!
\chapter{Targeting a Platform with
Maven}\label{targeting-a-platform-with-maven}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Although a Maven-based Liferay Workspace does not offer a configurable
property to set the target platform, you can still leverage the Target
Platform framework by adding a few dependencies to your project.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your workspace's root \texttt{pom.xml} file and add the following
dependencies:
\begin{verbatim}
com.liferay.portal
release.portal.bom
7.2.0
pom
import
com.liferay.portal
release.portal.bom.compile.only
7.2.0
pom
import
com.liferay.portal
release.portal.bom.third.party
7.2.0
pom
import
\end{verbatim}
These dependencies are described below:
\texttt{com.liferay.ce.portal.bom}: provides all the artifacts
included in Liferay DXP.
\texttt{com.liferay.ce.portal.compile.only}: provides artifacts that
are not included in Liferay DXP, but are necessary to reference during
the build (e.g., \texttt{org.osgi.core}).
\texttt{release.portal.bom.third.party}: provides all third party
artifacts that make up the Liferay Portal bundle.
Liferay DXP users must replace the artifact names and versions:
\begin{itemize}
\tightlist
\item
\texttt{release.portal.bom} → \texttt{release.dxp.bom}
\item
\texttt{release.portal.bom.compile.only} →
\texttt{release.dxp.bom.compile.only}
\item
\texttt{release.portal.bom.third.party}
\item
\texttt{7.2.0} → \texttt{7.2.10}
\end{itemize}
\item
Go through the remaining POMs in your workspace and remove
\texttt{\textless{}version\textgreater{}} tags for all
Liferay-specific artifacts. These versions are now being provided by
the Target Platform framework.
\end{enumerate}
Great! You can now target a platform in your Maven-based workspace.
\chapter{Validating Modules Against the Target
Platform}\label{validating-modules-against-the-target-platform}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
\noindent\hrulefill
\textbf{Important:} Validating modules with the \texttt{resolve} task is
deprecated. It only functions as it's documented here in versions prior
to Liferay Workspace (Gradle only) version 2.0.3. It is being redesigned
for workspace versions 2.0.3+ and is still in development at this time.
\noindent\hrulefill
After you write a module in Liferay Workspace, you can validate it
before deployment to make sure of several things:
\begin{itemize}
\tightlist
\item
Will my app deploy successfully?
\item
Will there be some sort of missing requirement?
\item
If there's an issue, how do I diagnose it?
\end{itemize}
These are all common worries that can be frustrating.
Instead of deploying your app and checking for errors in the log, you
can validate your app before deployment. This is done by calling Liferay
Workspace's \texttt{resolve} task, which validates your modules against
a targeted platform.
You'll cover the following topics in this section:
\begin{itemize}
\tightlist
\item
\hyperref[resolving-your-modules]{Resolving your modules}.
\item
\hyperref[modifying-the-target-platforms-capabilities]{Modifying the
target platform's capabilities}.
\item
\hyperref[including-the-resolver-in-your-gradle-build]{Including the
resolver in your Gradle build}.
\end{itemize}
Continue on to learn how this works.
\section{Resolving Your Modules}\label{resolving-your-modules}
You can resolve your modules before deployment. This can be done by
calling the \texttt{resolve} Gradle task provided by Liferay Workspace.
\begin{verbatim}
./gradlew resolve
\end{verbatim}
This task gathers all the capabilities provided by
\begin{itemize}
\tightlist
\item
the specified version of Liferay DXP (i.e.,
\href{/docs/7-2/reference/-/knowledge_base/r/managing-the-target-platform}{targeted
platform})
\item
the current workspace's modules
\end{itemize}
Some capabilities/information gathered by the \texttt{resolve} task that
are validated include
\begin{itemize}
\tightlist
\item
declared required capabilities
\item
module versions
\item
package imports/use constraints
\item
service references
\end{itemize}
It also computes a list of run requirements for your project. Then it
compares the current project's requirements against the gathered
capabilities. If your project requires something not available in the
gathered list of capabilities, the task fails.
The task can only validate OSGi modules. It does not work with WAR-style
projects, themes, or npm portlets.
\noindent\hrulefill
\textbf{Note:} The \texttt{resolve} task can be executed from a specific
project folder or from the workspace's root folder. Running the task
from the root folder validates all the modules in your workspace.
\noindent\hrulefill
The \texttt{resolve} task can automatically gather the available
capabilities from your workspace, but you must specify this for your
targeted Liferay DXP version. To do this, open your workspace's
\texttt{gradle.properties} file and set the
\texttt{liferay.workspace.target.platform.version} property to the
version you want to target. For example,
\begin{verbatim}
liferay.workspace.target.platform.version=7.2.0
\end{verbatim}
If you're using Liferay DXP, you can set the property like this:
\begin{verbatim}
liferay.workspace.target.platform.version=7.2.10
\end{verbatim}
The versions following a GA1 release of DXP follow fix pack versions
(e.g., \texttt{7.2.10.fp1}, \texttt{7.2.10.fp2}, etc.).
Setting the target platform property provides a static \emph{distro} JAR
for the specified version of Liferay DXP, which contains all the
metadata (i.e., capabilities, packages, versions, etc.) running in that
version. The distro JAR is a complete snapshot of everything provided in
the OSGi runtime; this serves as the target platform's list of
capabilities that your modules are validated against.
You can now validate your module projects before deploying them! If the
resolver throws errors, see the article on
\href{/docs/7-2/reference/-/knowledge_base/r/how-to-resolve-common-output-errors-reported-by-the-resolve-task}{how
to resolve common output errors reported by the \texttt{resolve} task}.
Sometimes, you must modify the \texttt{resolve} task's default behavior
to successfully validate your app. See the next section for more
information.
\section{Modifying the Target Platform's
Capabilities}\label{modifying-the-target-platforms-capabilities}
In a perfect world, everything the \texttt{resolve} task gathers and
checks against would work during your development process.
Unfortunately, there are exceptions that may force you to modify the
default functionality of the \texttt{resolve} task.
There are two scenarios you may run into during development that require
a modification for your project to pass the resolver check.
\begin{itemize}
\tightlist
\item
You're depending on a third party library that is not available in the
targeted Liferay DXP instance or the current workspace.
\item
You're depending on a customized distribution of Liferay DXP.
\end{itemize}
You'll explore these use cases next.
\section{Depending on Third Party Libraries Not Included in Liferay
DXP}\label{depending-on-third-party-libraries-not-included-in-liferay-dxp}
The \texttt{resolve} task, by default, gathers all of Liferay DXP's
capabilities and the capabilities of your workspace's modules. What if,
however, your module depends on a third party project that is not
included in either space (e.g.,
\href{https://opensource.google.com/projects/guava}{Google Guava})?. The
\texttt{resolve} task fails by default if your project depends on this
project type. You probably plan to have this project deployed and
available at runtime, so it's not a concern, but the resolver doesn't
know that; you must customize the resolver to bypass this.
There are three ways you can do this:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Embed
the third party library in your module}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/adding-a-third-party-librarys-capabilities-to-the-resolvers-capabilities}{Add
the third party library's capabilities to the current static set of
resolver capabilities}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/skipping-the-resolving-process-for-a-module}{Skip
the resolving process for your module}
\end{itemize}
\noindent\hrulefill
\textbf{Note:} You should only embed a third party library in your
module if it's the only module that depends on it. You should not bypass
the resolver failure this way if more than one project in the OSGi
container depends on that library.
\noindent\hrulefill
For help resolving third party dependency errors, see the
\href{/docs/7-1/frameworks/-/knowledge_base/frameworks/adding-third-party-libraries-to-a-module}{Resolving
Third Party Library Package Dependencies} tutorial.
\section{Depending on a Customized Distribution of Liferay
DXP}\label{depending-on-a-customized-distribution-of-liferay-dxp}
There are times when manually specifying your project's list of
dependent JARs does not suffice. If your app requires a customized
Liferay DXP instance to run, you must regenerate the target platform's
default list of capabilities with an updated list. Two examples of a
customized Liferay DXP instance are described below:
\textbf{Example 1: Leveraging an External Feature}
There are many external features/frameworks available that are not
included in the downloadable bundle by default. After deploying a
feature/framework, it's available for your module projects to leverage.
When validating your app, however, the \texttt{resolve} task does not
have access to external capabilities not included by default. For
example, Audience Targeting is an example of this type of external
framework. If you're creating a Liferay Audience Targeting rule that
depends on the Audience Targeting framework, you can't easily provide a
slew of JARs for your module. In this case, you should install the
platform your code depends on and regenerate an updated list of
capabilities that your Liferay DXP instance provides.
\textbf{Example 2: Leveraging a Customized Core Feature}
You can extend Liferay DXP's core features to provide a customized
experience for your intended audience. Once deployed, you can assume
these customizations are present and build other things on top of them.
The new capabilities resulting from your customizations are not
available, however, in the target platform's default list of
capabilities. Therefore, when your application relies on non-default
capabilities, it fails during the \texttt{resolve} task. To get around
this, you must regenerate a new list of capabilities that your
customized Liferay DXP instance provides.
To regenerate the target platform's capabilities (distro JAR) based on
the current workspace's Liferay DXP instance, follow the
\href{/docs/7-2/reference/-/knowledge_base/r/depending-on-a-customized-distribution-of-product}{Depending
on a Customized Distribution of Liferay DXP} article.
\section{Including the Resolver in Your Gradle
Build}\label{including-the-resolver-in-your-gradle-build}
By default, Liferay Workspace provides the \texttt{resolve} task as an
independent executable. It's provided by the
\href{/docs/7-2/reference/-/knowledge_base/r/target-platform-gradle-plugin}{Target
Platform} Gradle plugin and is not integrated in any other Gradle
processes. This gives you control over your Gradle build without
imposing strategies you may not want included in your default build
process.
With that said, the \texttt{resolve} task can be useful to include in
your build process if you want to check for errors in your module
projects before deployment. Instead of resolving your projects
separately from your standard build, you can build and resolve them all
in one shot.
In Liferay Workspace, the recommended path for doing this is adding it
to the default \texttt{check} Gradle task. The \texttt{check} task is
provided by default in a workspace by the
\href{https://docs.gradle.org/current/userguide/java_plugin.html\#_lifecycle_tasks}{Java}
plugin. Adding the \texttt{resolve} task to the \texttt{check} lifecycle
task also promotes the \texttt{resolve} task to run for CI and other
test tools that typically run the \texttt{check} task for verification.
Of course, Gradle's \texttt{build} task also depends on the
\texttt{check} task, so you can run \texttt{gradlew\ build} and run the
resolver too.
You can learn how to include the resolver in your Gradle build by
visiting
\href{/docs/7-2/reference/-/knowledge_base/r/including-the-resolver-in-your-gradle-build}{this
article}.
Continue on for various step-by-step instructions for
configuring/manipulating the resolver task.
\chapter{Adding a Third Party Library's Capabilities to the Resolver's
Capabilities}\label{adding-a-third-party-librarys-capabilities-to-the-resolvers-capabilities}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
You can add your third party dependencies to the target platform's
default list of capabilities by listing them as provided modules.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your workspace's root \texttt{build.gradle} file.
\item
Add a code snippet similar to this:
\begin{verbatim}
dependencies {
providedModules group: "GROUP_ID", name: "NAME", version: "VERSION"
}
\end{verbatim}
For example, if you wanted to add
\href{https://opensource.google.com/projects/guava}{Google Guava} as a
provided module, it would look like this:
\begin{verbatim}
dependencies {
providedModules group: "com.google.guava", name: "guava", version: "23.0"
}
\end{verbatim}
\end{enumerate}
This both provides the third party dependency to the resolver, and it
downloads and includes it in your Liferay DXP bundle's
\texttt{osgi/modules} folder when you initialize it (e.g.,
\texttt{blade\ server\ init}).
\chapter{Skipping the Resolving Process for a
Module}\label{skipping-the-resolving-process-for-a-module}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
It may be easiest to skip validating a particular module during the
resolve process.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your workspace's root \texttt{build.gradle} file.
\item
Insert the following Gradle code at the bottom of the file:
\begin{verbatim}
targetPlatform {
resolveOnlyIf { project ->
project.name != 'PROJECT_NAME'
}
}
\end{verbatim}
Be sure to replace the \texttt{PROJECT\_NAME} filler with your
module's name (e.g., \texttt{test-api}).
\item
(Optional) If you prefer to disable the Target Platform plugin
altogether, you can add a slightly different directive to your
\texttt{build.gradle} file:
\begin{verbatim}
targetPlatform {
onlyIf { project ->
project.name != 'PROJECT_NAME'
}
}
\end{verbatim}
This both skips the \texttt{resolve} task execution and disables BOM
dependency management.
\end{enumerate}
Now the \texttt{resolve} task skips your module project.
\chapter{Depending on a Customized Distribution of Liferay
DXP}\label{depending-on-a-customized-distribution-of-liferay-dxp-1}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
To regenerate the target platform's capabilities (distro JAR) based on
the current workspace's Liferay DXP instance, follow the steps below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Start the Liferay DXP instance stored in your workspace. Make sure the
platform you want to depend on is installed.
\item
Download the
\href{https://search.maven.org/\#search\%7Cga\%7C1\%7Cbiz.aqute.remote.agent}{BND
Remote Agent JAR file} and copy it into the \texttt{osgi/modules}
folder.
\item
From the root folder of your workspace, run the following command:
\begin{verbatim}
bnd remote distro -o custom_distro.jar release.portal.distro 7.2.0
\end{verbatim}
Liferay DXP users must replace the \texttt{release.portal.distro}
artifact name with \texttt{release.dxp.distro} and use the
\texttt{7.2.10} version syntax.
This connects to the newly deployed BND agent running in Liferay DXP
and generates a new distro JAR named \texttt{custom\_distro.jar}. All
other capabilities inherit their functionality based on your Liferay
DXP instance, so verify the workspace bundle is the version you plan
to release in production.
\item
Navigate to your workspace's root \texttt{build.gradle} file and add
the following dependency:
\begin{verbatim}
dependencies {
targetPlatformDistro files('custom_distro.jar')
}
\end{verbatim}
\end{enumerate}
Now your workspace is pointing to a custom distro JAR file instead of
the default one provided. Run the \texttt{resolve} task to validate your
modules against the new set of capabilities.
\chapter{Including the Resolver in Your Gradle
Build}\label{including-the-resolver-in-your-gradle-build-1}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
To call the \texttt{resolve} task during Gradle's \texttt{check} task
automatically, follow the instructions below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your workspace's root \texttt{build.gradle} file.
\item
Add the following directive:
\begin{verbatim}
check.dependsOn resolve
\end{verbatim}
The \texttt{resolve} task is now called during the \texttt{check}
task.
You can also configure this for specific projects in a workspace if
you don't want all modules to be included in the global
\texttt{check}.
\item
(Optional) If the \texttt{resolve} task runs during every Gradle
build, you may want to prevent the build from failing if there are
errors reported by the resolver. To do this, open your workspace's
root \texttt{build.gradle} file and add the following code:
\begin{verbatim}
targetPlatform {
ignoreResolveFailures = true
}
\end{verbatim}
This reports the failures without failing the build. Note, this can
only be configured in the workspace's root \texttt{build.gradle} file.
\end{enumerate}
Awesome! You can now run the \texttt{resolve} task in your current
Gradle lifecycle.
\chapter{How to Resolve Common Output Errors Reported by the Resolve
Task}\label{how-to-resolve-common-output-errors-reported-by-the-resolve-task}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay Workspace provides the \texttt{resolve} Gradle task to validate
modules. This is very useful for finding issues and reporting them as
output before deployment. For general help with OSGi related issues,
visit the
\href{/docs/7-2/appdev/-/knowledge_base/a/troubleshooting-application-development-issues}{Troubleshooting
FAQ} section.
For help interpreting the \texttt{resolve} task's output, see the list
below for common output errors, what they mean, and how to fix them.
\section{Missing Import Error}\label{missing-import-error}
When your module refers to an unavailable import, the container throws
this error. For example, suppose you have a module \texttt{test-service}
that depends on the \texttt{com.google.common.base} package. If the
container can't find that package, it throws this error:
\begin{verbatim}
Resolution exception in project 'modules:test-service': Unresolved requirements in root project 'modules:test-service':
Mandatory:
[osgi.wiring.package ] com.google.common.base; version=[23.0.0,24.0.0)
[osgi.identity ] test.service
\end{verbatim}
This kind of error can also occur when separate modules require
different versions of another module. If you have \emph{module A}
requiring \emph{module Test version 1} and \emph{module B} requiring
\emph{module Test version 4}, without running the resolver, both modules
A and B would compile successfully. When they were deployed, however,
one would fail in the OSGi runtime because both dependencies cannot be
satisfied. These types of scenarios are difficult to diagnose, but with
the \texttt{resolve} task, can be found with ease.
To fix missing import errors, you may need to adjust the
\href{/docs/7-2/customization/-/knowledge_base/c/exporting-packages}{export}
and/or
\href{/docs/7-2/customization/-/knowledge_base/c/importing-packages}{import}
configuration of your modules. Also, see the
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Resolving
Third Party Library Package Dependencies} tutorial for more information
on resolving import errors. Sometimes, this kind of error can be solved
by editing the \texttt{resolve} task's list of capabilities. See the
\href{/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module}{Resolving
Third Party Library Package Dependencies} section to learn how to do
this.
\section{Missing Service Reference}\label{missing-service-reference}
If your module references a non-existent service, an error is thrown.
This is helpful because service reference issues are hard to diagnose
during deployment without using the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
Shell}.
For example, if your module \texttt{test-portlet} references a service
(e.g., \texttt{test.api.TestApi}) it does not have access to, the
following error is thrown:
\begin{verbatim}
Resolution exception in project 'modules:test-portlet': Unresolved requirements in project 'modules:test-portlet':
Mandatory:
[osgi.identity ] test.portlet
[osgi.service ] objectClass=test.api.TestApi
\end{verbatim}
To fix this, you must make the service available to your module. If
you're expecting the service to be provided by your target platform,
check to make sure it's being provided. If it's a service provided by a
custom module, check that service provider module and ensure it's
correctly providing that service to your module. To check the target
platform for available services, follow the steps below:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Start your target platform instance.
\item
Open the Gogo shell.
\item
List all services containing a keyword by running
\texttt{services\ \textbar{}\ grep\ \ \ \ \ "SERVICE\_NAME"}. It's
easiest to do this rather than listing all services since there are
usually too many to sift through.
\item
You can also list services provided by a component. Run
\texttt{lb\ -s} to list all provided bundles by their bundle symbolic
name (BSN). Find the BSN for the desired component and then run
\texttt{scr:info\ \textless{}BSN\textgreater{}}.
\end{enumerate}
If you're unable to track down your missing service, it may be provided
by a customized Liferay DXP core feature or an external Liferay DXP
feature. If this is the case, it isn't included in the target platform's
default capabilities. You can make the custom service capability
available to reference by
\href{/docs/7-2/reference/-/knowledge_base/r/depending-on-a-customized-distribution-of-product}{generating
a new custom distro JAR}.
\section{Missing Fragment Host}\label{missing-fragment-host}
Referring to a non-existent fragment host throws an error. For example,
if your \texttt{test.login} fragment is configured to modify a fragment
host named \texttt{com.liferay.login.web} that cannot be referenced, the
following error is thrown:
\begin{verbatim}
Resolution exception in project 'modules:test.login': Unresolved requirements in project 'modules:test-login':
Mandatory:
[osgi.identity ] test.login
[osgi.wiring.host ] com.liferay.login.web; version=1.0.10
\end{verbatim}
Configuring a fragment host in your module is typically done with the
\texttt{Fragment-Host} header in the \texttt{bnd.bnd} file:
\begin{verbatim}
Fragment-Host: com.liferay.login.web;bundle-version="[1.0.0,1.0.1)"
\end{verbatim}
To fix this, inspect your target platform to ensure it includes the JAR
you're attempting to add a fragment for. Your fragment host header may
be referencing an incorrect bundle symbolic name (BSN) or version. The
easiest way to check this is by using the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
Shell}. Follow the steps below to find the bundle symbolic name:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Start your target platform instance.
\item
Open the Gogo shell.
\item
List all installed bundles by BSN with the command \texttt{lb\ -s}.
You can search through the output to find the BSN. If you already know
the BSN and want to check the version, run
\texttt{lb\ -s\ \textbar{}\ grep\ "\textless{}BSN\textgreater{}"}.
\end{enumerate}
Once you know the correct BSN/version to reference, update your
\texttt{Fragment-Host} header to resolve the error.
For more information on fragments, see the
\href{/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-osgi-fragments}{JSP
Overrides Using OSGi Fragments} tutorial.
\chapter{Validating Modules Outside of
Workspace}\label{validating-modules-outside-of-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
If you prefer to not use Liferay Workspace, but still want to validate
modules against a target platform, you must apply the
\href{/docs/7-2/reference/-/knowledge_base/r/target-platform-gradle-plugin}{Target
Platform Gradle plugin} to the root \texttt{build.gradle} file of your
multi-module Gradle build. Follow the
\href{/docs/7-2/reference/-/knowledge_base/r/targeting-a-platform-outside-of-workspace}{Targeting
a Platform Outside of Workspace} section to do this.
Once you have the Target Platform plugin and its BOM dependencies
configured, you must configure the \texttt{targetPlatformDistro}
dependency. Follow the instructions below to do this.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your project's root \texttt{build.gradle} file.
\item
Add the \texttt{targetPlatformDistro} dependency to the list of
dependencies. It should look like this:
\begin{verbatim}
dependencies {
targetPlatformBoms group: "com.liferay.portal", name: "release.portal.bom", version: "7.2.0"
targetPlatformBoms group: "com.liferay.portal", name: "release.portal.bom.compile.only", version: "7.2.0"
targetPlatformDistro group: "com.liferay.portal", name "release.portal.distro", version: "7.2.0"
}
\end{verbatim}
Liferay DXP users must replace the artifact names and versions:
\begin{itemize}
\tightlist
\item
\texttt{release.portal.bom} → \texttt{release.dxp.bom}
\item
\texttt{release.portal.bom.compile.only} →
\texttt{release.dxp.bom.compile.only}
\item
\texttt{release.portal.distro} → \texttt{release.dxp.distro}
\item
\texttt{7.2.0} → \texttt{7.2.10}
\end{itemize}
\end{enumerate}
Now you can validate your non-workspace modules against a target
platform!
\chapter{Leveraging Docker}\label{leveraging-docker}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Docker has become increasingly popular in today's development lifecycle,
by providing an automated way to package software and its dependencies
into a standardized unit that can be shared cross-platform. Read
Docker's extensive \href{https://docs.docker.com/}{documentation} to
learn more.
Liferay provides Docker images for
\begin{itemize}
\tightlist
\item
\href{https://hub.docker.com/r/liferay/portal}{Liferay Portal}
\item
\href{https://hub.docker.com/r/liferay/dxp}{Liferay DXP}
\item
\href{https://hub.docker.com/r/liferay/commerce}{Liferay Commerce}
\item
\href{https://hub.docker.com/r/liferay/portal-snapshot}{Liferay Portal
Snapshots}
\end{itemize}
You can pull Liferay's Docker images from those resources and manage
them yourself. Liferay Workspace, however, provides an easy way to
integrate Docker development into your existing development workflow
with preconfigured Gradle tasks.
The following Docker commands (Gradle-based) are available in Liferay
Workspace:
Command \textbar{} Description \texttt{buildDockerImage} \textbar{}
Builds the Docker image with all modules/configurations deployed.
\texttt{createDockerContainer} \textbar{} Creates a Docker container
from the Liferay DXP image and mounts the workspace's
\texttt{/build/docker} folder to the container's \texttt{/etc/liferay}
folder. \texttt{createDockerfile} \textbar{} Creates a
\texttt{Dockerfile} to build the Docker image. \texttt{dockerDeploy}
\textbar{} Deploys the project to the container's \texttt{deploy} folder
by copying the project archive file to workspace's
\texttt{build/docker/deploy} folder. This command can also be executed
from workspace's root folder to deploy all projects and copy all Docker
configurations (i.e., from the \texttt{configs/common} and
\texttt{configs/docker} folders) to the container.
\texttt{logsDockerContainer} \textbar{} Prints the portal runtime's
logs. You can exit log tracking mode while maintaining a running
container (e.g., {[}Ctrl\textbar Command{]} + C).
\texttt{pullDockerImage} \textbar{} Pulls the Docker image.
\texttt{removeDockerContainer} \textbar{} Removes the container from
Docker's system. \texttt{startDockerContainer} \textbar{} Starts the
Docker container. \texttt{stopDockerContainer} \textbar{} Stops the
Docker container.
\noindent\hrulefill
\textbf{Note:} Leveraging Docker in Liferay Workspace is only available
for Gradle projects at this time.
\noindent\hrulefill
In this section, you'll learn how to
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-product-docker-container}{Create
a Docker container based on a provided Liferay DXP image}.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/configuring-a-docker-container}{Configure
the container}.
\item
\href{/docs/7-2/reference/-/knowledge_base/r/building-a-custom-docker-image}{Build
a custom image}.
\end{itemize}
Continue on to learn more.
\chapter{Creating a Liferay DXP Docker
Container}\label{creating-a-liferay-dxp-docker-container}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
To create a Liferay DXP Docker container in Liferay Workspace, complete
the steps below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Choose the Docker image you need. This is configured in your
workspace's \texttt{gradle.properties} file by customizing this
property:
\begin{verbatim}
liferay.workspace.docker.image.liferay
\end{verbatim}
To find the possible property values you can set, see the official
Liferay DXP Docker Hub's Tags section (e.g.,
\href{https://hub.docker.com/r/liferay/portal/tags}{Liferay Portal
Docker Tags}). For example, if you want to base your container on the
Liferay Portal 7.2 GA1 image, you would set this property:
\begin{verbatim}
liferay.workspace.docker.image.liferay=liferay/portal:7.2.0-ga1
\end{verbatim}
\item
Run the following command from your workspace's root folder:
\begin{verbatim}
./gradlew createDockerContainer
\end{verbatim}
\end{enumerate}
This command creates a new container named
\texttt{{[}projectName{]}-liferayapp}. A new \texttt{build/docker}
folder is generated in your workspace. This folder is mounted into the
container's file system. This means files in workspace's
\texttt{build/docker} folder are also available in the container's
\texttt{/etc/liferay} folder.
Any projects in your workspace are automatically compiled and copied to
the \texttt{build/docker/deploy} folder when the container is created;
this means that when the container is started, all your projects are
deployed to the container. All configurations are also applied to the
container.
\noindent\hrulefill
\textbf{Note:} During your container's startup, you may run into the
following error:
\begin{verbatim}
/etc/liferay/entrypoint.sh: line 3: 11 Killed
${LIFERAY_HOME}/tomcat/bin/catalina.sh run
\end{verbatim}
This usually means you have not allocated enough memory to your Docker
engine to successfully run your container. See Docker's
\href{https://docs.docker.com}{documentation} to learn how to increase
resources available to Docker.
\noindent\hrulefill
Once your container is created, you can
\href{/docs/7-2/reference/-/knowledge_base/r/configuring-a-docker-container}{configure
it}.
\chapter{Configuring a Docker
Container}\label{configuring-a-docker-container}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Before starting your container, you may want to add additional portal
configurations. This could include things like
\begin{itemize}
\tightlist
\item
Property overrides (e.g., \texttt{portal-ext.properties})
\item
Marketplace app overrides
\item
App server configurations
\item
License files
\end{itemize}
You can do this by applying files (and their accompanying folder
structures, if necessary) to your workspace's \texttt{configs/docker}
folder. This folder is treated as your Liferay Home for Docker
development; you add additional files that overlay your workspace's
\texttt{configs/common} folder and your Liferay DXP container's default
configuration.
As an example, you'll enable the
\href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo
shell} for your container.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add a \texttt{portal-ext.properties} file to your workspace's
\texttt{configs/docker} folder.
\item
Add the following property to the \texttt{portal-ext.properties} file:
\begin{verbatim}
module.framework.properties.osgi.console=0.0.0.0:11311
\end{verbatim}
This lets you access your container using Gogo shell via telnet
session.
\item
Start the container.
Once the container is started, the configurations stored in
\texttt{configs/common} and \texttt{configs/docker} are transferred to
the \texttt{build/docker/files} folder, which applies all
configurations to the container's file system. For more information on
workspace's \texttt{configs} folder, see
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace\#testing-projects}{this
section}.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** You can call the `deployDocker` Gradle task from your
workspace's root folder to initiate the Docker configuration transfer to
the `build/docker/files` folder manually. It's executed automatically when
creating or starting the container.
\end{verbatim}
\noindent\hrulefill
You can now apply configurations to your Liferay DXP Docker container.
\chapter{Building a Custom Docker
Image}\label{building-a-custom-docker-image}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
You can preserve your container's configuration by building it as an
image.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Build your custom Liferay DXP image by running
\begin{verbatim}
./gradlew buildDockerImage
\end{verbatim}
A \texttt{Dockerfile} is generated for your container when building
your image. The \texttt{Dockerfile} is generated in your workspace's
\texttt{build/docker} folder. For more information on how to configure
the \texttt{Dockerfile}, see Docker's
\href{https://docs.docker.com/engine/reference/builder/}{Dockerfile
reference documentation}.
You can generate a \texttt{Dockerfile} manually at any time by running
\begin{verbatim}
./gradlew createDockerfile
\end{verbatim}
\item
Run \texttt{docker\ image\ ls} to verify the image's availability.
\end{enumerate}
You can now build Liferay DXP Docker images in Liferay Workspace!
\chapter{Updating Liferay Workspace}\label{updating-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay Workspace is continuously being updated with new features. If
you created your workspace a while ago, you may be missing out on some
of the latest features that could improve your Liferay DXP development
experience. Updating your Liferay Workspace is easy; you'll learn how to
do it for Gradle and Maven-based workspaces next.
\section{Gradle}\label{gradle-2}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Find the latest Liferay Workspace version. To do this, view the
Workspace Gradle plugin's
\href{https://repository-cdn.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.gradle.plugins.workspace/}{released
versions} on Liferay's repository. Copy the version to which you want
to upgrade.
\item
Open your Liferay Workspace's \texttt{settings.gradle} file. This file
resides in your Workspace's root folder.
\item
In the \texttt{dependencies} block, you'll find code similar to below:
\begin{verbatim}
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.workspace", version: "[WORKSPACE_VERSION]"
}
\end{verbatim}
Update the \texttt{com.liferay.gradle.plugins.workspace} dependency's
\texttt{version} to the version number you copied in step 1.
\item
Execute any Gradle command to initiate the update process for your
Workspace (e.g., \texttt{blade\ gw\ tasks}).
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} The Gradle wrapper provided in a Gradle-based Liferay
Workspace must be updated if you're migrating from a workspace before
version \texttt{1.10.14} to the latest available version. To update your
Gradle wrapper, run
\begin{verbatim}
./gradlew wrapper --gradle-version=4.10.2
\end{verbatim}
\noindent\hrulefill
Awesome! You've upgraded your Gradle-based Liferay Workspace!
\section{Maven}\label{maven-4}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Find the latest Liferay Workspace version. To do this, view the Bundle
Support plugin's
\href{https://repository-cdn.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.portal.tools.bundle.support/}{released
versions} on Liferay's repository. Copy the version to which you want
to upgrade.
\item
Open your Liferay Workspace's root \texttt{pom.xml} file.
\item
Within the \texttt{plugin} tags, you'll find code similar to below:
\begin{verbatim}
com.liferay
com.liferay.portal.tools.bundle.support
3.4.2
...
\end{verbatim}
Update the \texttt{com.liferay.portal.tools.bundle.support} artifact's
\texttt{version} to the version number you copied in step 1.
\item
Execute any Maven command to initiate the update process for your
Workspace (e.g., \texttt{mvn\ verify}).
\end{enumerate}
Awesome! You've upgraded your Maven-based Liferay Workspace!
\chapter{Updating Default Plugins Provided by Liferay
Workspace}\label{updating-default-plugins-provided-by-liferay-workspace}
{This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
Liferay Workspace comes with a slew of plugins like these:
\begin{itemize}
\tightlist
\item
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/css-builder}{CSS
Builder}
\item
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/javadoc-formatter}{Javadoc
Formatter}
\item
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/lang-builder}{Lang
Builder}
\item
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-service-builder}{Service
Builder}
\item
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/source-formatter}{Source
Formatter}
\item
\href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-theme-builder}{Theme
Builder}
\item
etc.
\end{itemize}
Bundled plugins are updated with each release of workspace. Suppose you
need a new feature in the
\href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.source.formatter/}{Source
Formatter plugin}, but the latest workspace version has not yet been
updated to include it. You can upgrade it yourself!
To upgrade one of workspace's bundled plugins, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Find the bundle symbolic name (BSN) for the plugin you want to update.
You can find this value in the
\href{https://github.com/liferay/liferay-portal/blob/master/modules/sdk/gradle-plugins/src/main/resources/com/liferay/gradle/plugins/dependencies/portal-tools.properties}{\texttt{portal-tools.properties}}
file. For example, the Source Formatter's BSN is
\texttt{com.liferay.source.formatter}.
\item
Open your workspace's \texttt{build.gradle} file and copy the plugin's
BSN followed by \texttt{.version} and set the desired plugin version
you want to use. For example,
\begin{verbatim}
com.liferay.source.formatter.version=1.0.819
\end{verbatim}
If you're most interested in the latest and greatest plugins, you can
set the above property to \texttt{latest.release} to always use the
latest available version. This could, however, cause your workspace to
become unstable.
\end{enumerate}
That's it! You're no longer tied to particular plugin versions provided
by your workspace.
\chapter{Maven}\label{maven-5}
\href{https://maven.apache.org/}{Maven} is a viable option for managing
Liferay projects if you don't want to use Liferay's default Gradle
management system. Liferay provides several
\href{/docs/7-2/reference/-/knowledge_base/r/maven-plugins}{Maven
plugins} for generating and managing your project. Liferay also provides
easy to obtain Maven artifacts that are required for Liferay Maven
module development. Here, you'll learn how to
\begin{itemize}
\tightlist
\item
Install Liferay Maven artifacts
\item
Create/Manage a Maven Repository
\item
Apply Maven plugins
\end{itemize}
Because Liferay DXP is tool-agnostic, Maven is fully supported for
Liferay DXP development. Read on for details about these topics.
\section{Installing Liferay Maven
Artifacts}\label{installing-liferay-maven-artifacts}
To create Liferay projects using Maven, you'll need its dependencies.
This isn't a problem---Liferay provides them as Maven artifacts. You can
retrieve them from a remote repository.
There are two remote repositories that contain Liferay artifacts:
Central Repository and Liferay Repository. The Central Repository is the
default repository used to download artifacts if you don't have a remote
repository configured. Using the Central Repository to install Liferay
Maven artifacts only requires that you
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{specify
your module's dependencies} in its \texttt{pom.xml} file.
When packaging your module, the automatic Maven artifact installation
process only downloads the artifacts necessary for that module from the
Central Repository.
The Central Repository \emph{usually} offers the latest Liferay Maven
artifacts, but the Liferay Repository \emph{guarantees} access to the
latest artifacts released by Liferay. Other than a slight delay in
artifact releases, the two repositories are identical. When the Liferay
repository is configured in your \texttt{settings.xml} file, archetypes
are generated based on that repository's contents. The Liferay Maven
repository offers a good alternative for those who want the most
up-to-date Maven artifacts produced by Liferay.
\noindent\hrulefill
\textbf{Note:} If you've configured the Liferay Nexus repository to
access Liferay Maven artifacts and you've already been syncing from the
Central Repository, you might have to clear out parts of your local
repository to force Maven to re-download the newer artifacts. Also,
don't leave the Liferay repository enabled when publishing artifacts to
Maven Central. You must comment out the Liferay Repository credentials
when publishing your artifacts.
\noindent\hrulefill
Next, you'll learn about managing your Maven artifacts.
\section{Managing Maven Artifacts in a
Repository}\label{managing-maven-artifacts-in-a-repository}
You can share Liferay artifacts and modules with teammates or manage
your repositories using a GUI by using
\href{http://www.sonatype.org/nexus/}{Sonatype Nexus}. It's a Maven
repository management server for creating and managing release servers,
snapshot servers, and proxy servers. There are several other Maven
repository management servers you can use (for example,
\href{https://www.jfrog.com/artifactory/}{Artifactory}), but this
section focuses on Nexus.
You'll learn how to
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-maven-repository}{Create
a repository}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/configuring-local-maven-settings-to-access-repositories}{Configure
a repository}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-liferay-maven-artifacts-to-a-repository}{Deploy
artifacts to a repository}
\end{itemize}
Before using repository servers, you must specify them in your Maven
environment settings. Your repository settings let Maven find the
repository and retrieve and install artifacts. You can configure your
local Maven settings in the \texttt{{[}USER\_HOME{]}/.m2/settings.xml}
file.
\noindent\hrulefill
\textbf{Note}: You must only configure a repository server if you're
sharing artifacts (e.g., Liferay artifacts and/or your modules) with
others. If you're installing Liferay artifacts from the Central/Liferay
Repository and aren't interested in sharing artifacts, you don't need a
repository server specified in your Maven settings. You can find out
more about installing artifacts from the Central Repository or Liferay's
own Nexus repository in the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-remote-liferay-maven-artifacts}{Installing
Remote Liferay Maven Artifacts} article.
\noindent\hrulefill
To deploy to a remote repository, your Liferay project should be
packaged using Maven. Maven provides a packaging command that creates an
artifact (JAR) that can be easily deployed to your remote repository.
Once you've created a deployable artifact, you can configure your module
project to communicate with your remote repository and use Maven's
\texttt{deploy} command to send it on its way. Once your module project
resides on the remote repository, other developers can configure your
remote repository in their projects and set dependencies in their
project POMs to reference it.
\section{Applying Maven Plugins}\label{applying-maven-plugins}
There are several important Maven plugins that provide important
functionality to Liferay Maven projects. The available Liferay Maven
plugins are available in the
\href{/docs/7-2/reference/-/knowledge_base/r/maven-plugins}{Maven
Plugins} section.
The following tasks are covered in this section:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/reference/-/knowledge_base/r/building-an-osgi-module-jar-with-maven}{Building
an OSGi module JAR}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/building-a-theme-with-maven}{Building
themes}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/compiling-sass-files-in-a-maven-project}{Compiling
Sass files}
\item
\href{/docs/7-2/reference/-/knowledge_base/r/using-service-builder-in-a-maven-project}{Using
Service Builder}
\end{itemize}
Read on to learn more!
\chapter{Installing Liferay Maven
Artifacts}\label{installing-liferay-maven-artifacts-1}
If you haven't configured your project to retrieve artifacts from a
custom Maven repository, your project will leverage the artifacts from
the Central Repository. You can view these artifacts from the
\href{https://search.maven.org/}{Maven Central Repository} site. Use the
Latest Version column as a guide to see what's available for the version
of Liferay DXP you're developing for.
If you'd like to access Liferay's latest released Maven artifacts,
configure Maven to use
\href{https://repository-cdn.liferay.com}{Liferay's Nexus repository}
instead. To do this, open your project's parent \texttt{pom.xml} and add
this:
\begin{verbatim}
liferay-public-releases
Liferay Public Releases
https://repository-cdn.liferay.com/nexus/content/repositories/liferay-public-releases
liferay-public-releases
https://repository-cdn.liferay.com/nexus/content/repositories/liferay-public-releases/
\end{verbatim}
The above configuration retrieves artifacts from Liferay's release
repository.
If you're most interested in retrieving Liferay's latest snapshot
artifacts, follow the instructions below to configure Liferay's Nexus
repository to access them.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your project's parent \texttt{pom.xml} and add this:
\begin{verbatim}
liferay-public-snapshots
Liferay Public Snapshots
https://repository-cdn.liferay.com/nexus/content/repositories/liferay-public-snapshots
liferay-public-snapshots
https://repository-cdn.liferay.com/nexus/content/repositories/liferay-public-snapshots/
\end{verbatim}
\item
Enable your project to access snapshot artifacts by adding this code
to your parent project's \texttt{pom.xml}:
\begin{verbatim}
true
\end{verbatim}
\end{enumerate}
You're now equipped to access Liferay's Maven artifacts via the
\begin{itemize}
\tightlist
\item
Central Repository
\item
Liferay Repository (releases)
\item
Liferay repository (snapshots)
\end{itemize}
Great job!
\chapter{Creating a Maven Repository}\label{creating-a-maven-repository}
To create a Maven repository using Nexus, download
\href{https://help.sonatype.com/display/NXRM2/Download}{Nexus} and
follow the instructions on Nexus'
\href{https://help.sonatype.com/display/NXRM2/Installing+and+Running}{Installation
page} to install and start it.
To create your own repository using Nexus, follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your web browser; navigate to your Nexus repository server (e.g.,
\url{http://localhost:8081/nexus}) and log in. The default user name
is \texttt{admin} with password \texttt{admin123}.
\item
Click on \emph{Repositories} and navigate to \emph{Add\ldots{}} →
\emph{Hosted Repository}.
\begin{figure}
\centering
\includegraphics{./images/maven-nexus-create-repo.png}
\caption{Adding a repository to hold your Liferay artifacts is easy
with Nexus.}
\end{figure}
To learn more about each type of Nexus repository, read Sonatype's
\href{http://books.sonatype.com/nexus-book/reference/confignx-sect-manage-repo.html}{Managing
Repositories} guide.
\item
Enter repository properties appropriate for the type of artifacts it
will hold. If you're installing release version artifacts into the
repository, specify \emph{Release} as the repository policy. Below are
example repository property values:
\begin{itemize}
\tightlist
\item
\textbf{Repository ID:} \emph{liferay-releases}
\item
\textbf{Repository Name:} \emph{Liferay Release Repository}
\item
\textbf{Provider:} \emph{Maven2}
\item
\textbf{Repository Policy:} \emph{Release}
\end{itemize}
To create a snapshot repository, choose \emph{Snapshot} for the
Repository Policy and update the ID and name accordingly.
\item
Click \emph{Save}.
\end{enumerate}
Voila! You've created a repository for your Liferay releases (i.e.,
\texttt{liferay-releases}) and/or Liferay snapshots (i.e.,
\texttt{liferay-snapshots}). To learn how to deploy your Liferay Maven
artifacts to a Nexus repository, see the
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-liferay-maven-artifacts-to-a-repository}{Deploying
Liferay Maven Artifacts to a Repository} tutorial.
See the
\href{/docs/7-2/reference/-/knowledge_base/r/configuring-local-maven-settings-to-access-repositories}{Configuring
Local Maven Settings to Access Repositories} to configure your new
repository servers in your Maven settings to install artifacts to them.
\chapter{Configuring Local Maven Settings to Access
Repositories}\label{configuring-local-maven-settings-to-access-repositories}
To configure your Maven environment to access your repository servers,
do the following:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to your \texttt{{[}USER\_HOME{]}/.m2/settings.xml} file.
Create it if it doesn't yet exist.
\item
Configure your repository server settings. Here are contents from a
\texttt{settings.xml} file that has \texttt{liferay-releases} and
\texttt{liferay-snapshots} repository servers configured:
\begin{verbatim}
liferay-releases
admin
admin123
liferay-snapshots
admin
admin123
\end{verbatim}
The user name \texttt{admin} and password \texttt{admin123} are the
credentials of the default Nexus administrator account. If you changed
these credentials for your Nexus server, make sure to update
\texttt{settings.xml} with these changes.
\end{enumerate}
Now that your repositories are configured, they're ready to receive all
the Liferay Maven artifacts you'll download and the Liferay module
artifacts you'll create!
\chapter{Deploying Liferay Maven Artifacts to a
Repository}\label{deploying-liferay-maven-artifacts-to-a-repository}
Deploying artifacts to a remote repository is important if you intend to
share your Maven projects with others. First, you must have a remote
repository that can hold deployed Maven artifacts. If you do not
currently have a remote repository,
\href{/docs/7-2/reference/-/knowledge_base/r/creating-a-maven-repository}{create
one}. Also make sure your \texttt{{[}USER\_HOME{]}/.m2/settings.xml}
file specifies your remote repository's ID, user name, and password
(configuration instructions
\href{/docs/7-2/reference/-/knowledge_base/r/configuring-local-maven-settings-to-access-repositories}{here}).
To follow this article, you'll need a Liferay module built with Maven.
For demonstration purposes, this tutorial uses the \texttt{portlet.ds}
sample module project. To follow along with this module, download the
\href{https://portal.liferay.dev/documents/113763090/114000186/portlet.ds.zip}{portlet.ds}
Zip.
Now it's time to deploy a Maven artifact to your Nexus repository.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Create a folder anywhere on your machine to serve as the parent folder
for your Liferay modules. Unzip the \texttt{portlet.ds} module project
into that folder.
\item
Create a \texttt{pom.xml} file inside this folder. Copy the following
logic into the parent POM:
\begin{verbatim}
4.0.0
liferay.sample
liferay.sample.maven
1.0.0
Liferay Maven Module Projects
pom
liferay-releases
http://localhost:8081/nexus/content/repositories/liferay-releases
portlet.ds
\end{verbatim}
The tags \texttt{\textless{}modelVersion\textgreater{}} through
\texttt{\textless{}packaging\textgreater{}} are POM tags that are used
frequently in parent POMs. Visit Maven's
\href{https://maven.apache.org/pom.html}{POM Reference} documentation
for more information.
The \texttt{\textless{}distributionManagement\textgreater{}} tag
specifies the deployment repository for all module projects residing
in the parent folder. This repository should also be specified in your
\texttt{{[}USER\_HOME{]}/.m2/settings.xml}. Both the parent POM and
\texttt{settings.xml} file's repository declarations are required to
deploy your modules to that remote repository.
Finally, you must list the modules residing in the parent folder that
you want deployed using the \texttt{\textless{}modules\textgreater{}}
tag. The \texttt{portlet.ds} module is specified within that tag.
\item
Open the \texttt{portlet.ds} module's \texttt{pom.xml} file. If you
did not download the \texttt{portlet.ds} module project Zip, you can
reference its POM below.
\begin{verbatim}
4.0.0
portlet.ds
1.0.0
jar
liferay.sample
liferay.sample.maven
1.0.0
../pom.xml
javax.portlet
portlet-api
2.0
provided
org.osgi
org.osgi.service.component.annotations
1.3.0
provided
\end{verbatim}
The \texttt{portlet.ds} module's POM specifies its own attributes
first, followed by the parent POM's attributes. Declaring the
\texttt{\textless{}parent\textgreater{}} tag like above links the
\texttt{portlet.ds} module to its parent POM, which is necessary to
deploy to the remote repository. Then the module's dependencies are
listed. These dependencies are downloaded from the Central Repository
and installed to your local \texttt{.m2} repository when you package
the \texttt{portlet.ds} module.
\item
Now that you've configured your parent POM and module POM, package
your Maven project. Navigate to your module project (e.g.,
\texttt{project.ds}) using the command line and run the Maven package
command:
\begin{verbatim}
mvn package
\end{verbatim}
This downloads and installs all your module's dependencies and
packages the project into a JAR file. Navigate to your module
project's generated build folder (e.g., \texttt{/target}). You'll
notice there is a newly generated JAR file. This is the artifact
you'll deploy to your Nexus repository.
\item
Run Maven's deploy command to deploy your module project's artifact to
your configured remote repository.
\begin{verbatim}
mvn deploy
\end{verbatim}
Your console shows output from the artifact being deployed into your
repository server.
\end{enumerate}
To verify that your artifact is deployed, navigate to the
\emph{Repositories} page of your Nexus server and select your
repository. A window appears below showing the Liferay artifact now
deployed to your repository.
\begin{figure}
\centering
\includegraphics{./images/maven-verify-deployment.png}
\caption{Your repository server now provides access to your Liferay
Maven artifacts.}
\end{figure}
Awesome! You can now share your Liferay module projects with anyone by
deploying them as artifacts to your remote repository!
\chapter{Building an OSGi Module JAR with
Maven}\label{building-an-osgi-module-jar-with-maven}
If you have an existing Liferay module built with Maven that you created
from scratch, or you're upgrading your Maven project from a previous
version of Liferay DXP, your project probably can't generate an
executable OSGi JAR. Don't fret! You can do this by making a few minor
configurations in your module's POMs.
\noindent\hrulefill
\textbf{Note:} If you used Liferay's Maven archetypes to generate your
module project, the project already has the Maven plugins required to
generate an OSGi JAR.
\noindent\hrulefill
Continue on to see how this is done.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your project's \texttt{pom.xml} file, add the
\href{http://njbartlett.name/2015/03/27/announcing-bnd-maven-plugin.html}{BND
Maven Plugin} declaration:
\begin{verbatim}
biz.aQute.bnd
bnd-maven-plugin
3.3.0
bnd-process
biz.aQute.bnd
biz.aQute.bndlib
3.2.0
com.liferay
com.liferay.ant.bnd
2.0.41
\end{verbatim}
The BND Maven plugin prepares all your Maven module's resources (e.g.,
\texttt{MANIFEST.MF}) and inserts them into the generated
\texttt{{[}Maven\ Project{]}/target/classes} folder. This plugin
prepares your module to be packaged as an OSGi JAR deployable to
Liferay DXP.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Although WABs can be generated using the `bnd-maven-plugin`,
this is not supported by Liferay. WABs should be created as a standard WAR
project and deployed to the
[Liferay WAB Generator](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator),
which generates a WAB for you.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
In your project's \texttt{pom.xml} file, add the
\href{http://maven.apache.org/plugins/maven-jar-plugin/}{Maven JAR
Plugin} declaration:
\begin{verbatim}
org.apache.maven.plugins
maven-jar-plugin
2.6
${project.build.outputDirectory}/META-INF/MANIFEST.MF
\end{verbatim}
The Maven JAR plugin builds your Maven project as a JAR file,
including the resources generated by the BND Maven plugin. The above
configuration also sets the default project \texttt{MANIFEST.MF} file
path for your project, which is essential when packaging your module
using the BND Maven plugin. By default, the Maven JAR Plugin ignores
the \texttt{target/classes/META-INF/MANIFEST.MF} generated by the BND
Maven plugin, so you must explicitly set it as the manifest file so
it's included properly in the generated JAR file.
\item
Add a \href{http://bnd.bndtools.org/}{\texttt{bnd.bnd} file} to your
Liferay Maven project, residing in the same folder as your project's
\texttt{pom.xml} file.
\item
Build your Maven OSGi JAR by running
\begin{verbatim}
mvn package
\end{verbatim}
Your Maven JAR is generated in your project's \texttt{/target} folder.
You can deploy it manually into Liferay DXP's \texttt{/deploy} folder,
or you can configure your project to deploy automatically to Liferay
DXP by following the
\href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project\#maven}{Deploying
a Project} article.
\end{enumerate}
Fantastic! You've configured your Liferay Maven project to package
itself into a deployable OSGi module.
\chapter{Building a Theme with Maven}\label{building-a-theme-with-maven}
Liferay's Theme Builder is used to build Liferay DXP theme files in your
project. You can incorporate the Theme Builder into your Maven project
to generate WAR-style
\href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{themes}
deployable to Liferay DXP.
The easiest way to create a Liferay theme with Maven is to create a new
Maven project using Liferay's provided
\href{/docs/7-2/reference/-/knowledge_base/r/theme-template}{Theme
archetype}; Theme Builder is configured in the new project by default.
In some cases, however, this may not be convenient. For instance, if you
have a legacy theme project and don't want to start over, generating a
new project is not ideal.
For cases like this, you should manually configure your Maven project to
leverage Theme Builder. You'll learn how to do this next.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Configure Liferay's
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-plugin}{Theme
Builder} plugin in your project's \texttt{pom.xml} file:
\begin{verbatim}
com.liferay
com.liferay.portal.tools.theme.builder
1.1.7
generate-resources
build
${maven.war.src}
${project.artifactId}
${project.build.directory}/${project.build.finalName}
${project.build.directory}/deps/com.liferay.frontend.theme.styled.jar
_styled
ftl
${project.build.directory}/deps/com.liferay.frontend.theme.unstyled.jar
\end{verbatim}
The above configuration applies the Theme Builder plugin and then
defines the Theme Builder's execution and configuration.
\begin{itemize}
\tightlist
\item
The
\href{https://maven.apache.org/guides/mini/guide-configuring-plugins.html\#Using_the_executions_Tag}{executions}
tag configures the Theme Builder to run during the
\texttt{generate-resources} phase of your Maven project's build
lifecycle. The \texttt{build}
\href{http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html\#A_Build_Phase_is_Made_Up_of_Plugin_Goals}{goal}
is defined for that lifecycle phase.
\item
The \href{https://maven.apache.org/pom.html\#Plugins}{configuration}
defines tag several important properties. For more info on these
properties, see the
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-plugin}{Theme
Builder Plugin} article.
\end{itemize}
\item
Apply the CSS Builder plugin, which is required to use Theme Builder:
\begin{verbatim}
com.liferay
com.liferay.css.builder
2.1.3
default-build
compile
build
target/${project.build.finalName}
/
target/deps/com.liferay.frontend.css.common.jar
\end{verbatim}
You can learn more about the CSS Builder's Maven configuration by
visiting the
\href{/docs/7-2/reference/-/knowledge_base/r/compiling-sass-files-in-a-maven-project}{Compiling
Sass Files in a Maven Project} tutorial.
\item
You can configure your project to exclude Sass files from being
packaged in your theme. This is optional, but is a nice convenience to
keep any unnecessary source code out of your WAR. Since the Theme
Builder creates a WAR-style theme, you should apply the
\href{https://maven.apache.org/plugins/maven-war-plugin/}{maven-war-plugin}
so it instructs the WAR file packaging process to exclude Sass files:
\begin{verbatim}
maven-war-plugin
3.0.0
**/*.scss
\end{verbatim}
\item
Insert the \texttt{\textless{}packaging\textgreater{}} tag in your
project's POM so your project is correctly packaged as a WAR file.
This tag can be placed with your project's \texttt{groupId},
\texttt{artifactId}, and \texttt{version} specifications like this:
\begin{verbatim}
com.liferay
com.liferay.project.templates.theme
1.0.0
war
\end{verbatim}
\item
Building themes requires certain dependencies. You can
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{configure
these dependenices} in your project's \texttt{pom.xml} as directories
or JAR files. If you choose to use JARs, you must apply the
\href{http://maven.apache.org/plugins/maven-dependency-plugin/}{maven-dependency-plugin}
and have it copy JAR dependencies into your project from Maven
Central:
\begin{verbatim}
maven-dependency-plugin
generate-sources
copy
com.liferay
com.liferay.frontend.css.common
${com.liferay.frontend.css.common.version}
com.liferay
com.liferay.frontend.theme.styled
${com.liferay.frontend.theme.styled.version}
com.liferay
com.liferay.frontend.theme.unstyled
${com.liferay.frontend.theme.unstyled.version}
${project.build.directory}/deps
true
\end{verbatim}
This configuration copies the
\texttt{com.liferay.frontend.css.common},
\texttt{com.liferay.frontend.theme.styled}, and
\texttt{com.liferay.frontend.theme.unstyled} dependencies into your
Maven project. Notice that you've set the \texttt{stripVersion} tag to
\texttt{true} and you're setting the artifact versions within each
\texttt{artifactItem} tag. You'll set these versions and a few other
properties for your Maven project next.
\item
Configure the properties for your project in its \texttt{pom.xml}
file:
\begin{verbatim}
2.1.3
2.0.4
2.0.28
2.2.5
1.1.7
\end{verbatim}
The properties above set the versions for the CSS and Theme Builder
plugins and their dependencies.
\end{enumerate}
You've successfully configured your Maven project to build a Liferay
theme with Theme Builder!
\chapter{Compiling Sass Files in a Maven
Project}\label{compiling-sass-files-in-a-maven-project}
If your Liferay Maven project uses Sass files to style its UI, you must
configure the project to convert its Sass files into CSS files so they
are recognizable for Maven's build lifecycle. It would be a real pain to
convert your Sass files into CSS files manually before building your
Maven project!
Liferay provides the
\href{/docs/7-2/reference/-/knowledge_base/r/css-builder-plugin}{CSS
Builder} plugin, which converts Sass files into CSS files so the Maven
build can parse your style sheets.
Here's how to apply Liferay's CSS builder to your Maven project.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open your project's \texttt{pom.xml} file and apply Liferay's CSS
Builder:
\begin{verbatim}
com.liferay
com.liferay.css.builder
2.1.0
default-build
compile
build
${com.liferay.portal.tools.theme.builder.outputDir}
/
target/deps/com.liferay.frontend.css.common.jar
\end{verbatim}
The above configuration applies the CSS Builder and then defines the
CSS Builder's execution and configuration.
\begin{itemize}
\tightlist
\item
The
\href{https://maven.apache.org/guides/mini/guide-configuring-plugins.html\#Using_the_executions_Tag}{executions}
tag configures the CSS Builder to run during the \texttt{compile}
phase of your Maven project's build lifecycle. The \texttt{build}
\href{http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html\#A_Build_Phase_is_Made_Up_of_Plugin_Goals}{goal}
is defined for that lifecycle phase.
\item
The \href{https://maven.apache.org/pom.html\#Plugins}{configuration}
tag defines two important properties. For more info on these
properties, see the
\href{/docs/7-2/reference/-/knowledge_base/r/css-builder-plugin}{CSS
Builder Plugin} article.
\end{itemize}
\item
If you're using \href{http://bourbon.io/}{Bourbon} in your Sass files,
you'll need to
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{add
an additional plugin dependency} to your project's POM. If you're not
using Bourbon, skip this step. Add the following plugin dependency:
\begin{verbatim}
maven-dependency-plugin
generate-sources
copy
com.liferay
com.liferay.frontend.css.common
2.0.4
${project.build.directory}/deps
true
\end{verbatim}
The
\href{http://maven.apache.org/plugins/maven-dependency-plugin/}{maven-dependency-plugin}
copies the \texttt{com.liferay.frontend.css.common} dependency from
Maven Central to your project's build folder so the CSS Builder can
leverage it.
\item
Use this command to compile your Maven project's Sass files:
\begin{verbatim}
mvn compile
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} Liferay's CSS Builder is supported for Oracle's JDK and
uses a native compiler for increased speed. If you're using an IBM JDK,
you may experience issues when building your Sass files (e.g., when
building a theme). It's recommended to switch to using the Oracle JDK,
but if you prefer using the IBM JDK, you must use the fallback Ruby
compiler. To do this, add the following tag to your CSS Builder
configuration in your POM:
\begin{verbatim}
ruby
\end{verbatim}
Be aware that the Ruby-based compiler doesn't perform as well as the
native compiler, so expect longer compile times.
\noindent\hrulefill
Awesome! You can now compile Sass files in your Liferay Maven project.
\chapter{Using Service Builder in a Maven
Project}\label{using-service-builder-in-a-maven-project}
The easiest way to add
\href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service
Builder} to your Maven project is to create a new Maven project using
Liferay's provided Service Builder archetype; it's configured in the new
project by default. You can learn how to generate a Service Builder
Maven project by visiting the
\href{/docs/7-2/reference/-/knowledge_base/r/using-the-service-builder-template}{Service
Builder Template} article.
In some cases, you should not use this template due to a number of
reasons:
\begin{itemize}
\tightlist
\item
You're updating a legacy Maven project to follow OSGi modular
architecture.
\item
You have a pre-existing modular Maven project and don't want to start
over.
\end{itemize}
For these cases, you can configure Service Builder in your project
manually. Follow the instructions below to configure Service Builder in
your Maven project!
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Apply the Service Builder plugin in your Maven project's
\texttt{pom.xml} file:
\begin{verbatim}
com.liferay
com.liferay.portal.tools.service.builder
1.0.276
../basic-api/src/main/java
true
true
true
src/main/resources/META-INF/module-hbm.xml
src/main/java
service.xml
src/main/resources/META-INF/portlet-model-hints.xml
src/main/resources/META-INF/portlet-model-hints.xml
true
com.liferay.blade.samples.servicebuilder.service.util.PropsUtil
src/main/resources
src/main/resources/META-INF/spring/module-spring.xml
beans,osgi
src/main/resources/META-INF/sql
tables.sql
src/main/test
\end{verbatim}
The \texttt{configuration} tag used above is an example of what a
Service Builder project's configuration could look like. All the
configuration attributes above should be modified to fit your project.
The above code configures Service Builder for a sample
\texttt{basic-service} module. When running Service Builder with this
configuration, the project's API classes are generated in the
\texttt{basic-api} module's \texttt{src/main/java} folder. You can
also specify your hibernate module mappings, implementation directory
name, model hints file, Spring configurations, SQL configurations,
etc. You can reference all the configurable Service Builder properties
in the
\href{/docs/7-2/reference/-/knowledge_base/r/service-builder-plugin}{Service
Builder Plugin} reference article.
\item
Apply the WSDD Builder plugin declaration directly after the Service
Builder plugin declaration:
\begin{verbatim}
com.liferay
com.liferay.portal.tools.wsdd.builder
1.0.10
service.xml
src/main/java
src/main/resources/server-config.wsdd
\end{verbatim}
The WSDD Builder is required to generate your project's remote
services.
See the
\href{/docs/7-2/reference/-/knowledge_base/r/wsdd-builder-plugin}{WSDD
Builder Plugin} article for more information on the configuration
properties.
\end{enumerate}
Terrific! You've successfully configured your Maven project to use
Service Builder by applying the Service Builder and WSDD Builder plugins
in your project's POM.
\chapter{Upgrading Your Maven Build
Environment}\label{upgrading-your-maven-build-environment}
\noindent\hrulefill
\textbf{Note:} This upgrade article only applies to projects residing in
a pre Liferay Portal 7.0 Maven environment that are not upgrading to
Liferay Workspace. If you're interested in upgrading to a Maven-based
Liferay Workspace (recommended), see the
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{Upgrading
Code to 7.0} tutorials for more information.
\noindent\hrulefill
If you're an avid Maven user and have been using it for Liferay Portal
6.2 project development or older, you must upgrade your Maven build to
be compatible with 7.0 development. There are two main parts of the
Maven environment upgrade process that you must address:
\begin{itemize}
\tightlist
\item
\hyperref[upgrading-to-new-product-ver-maven-plugins]{Upgrading to new
7.0 Maven plugins}
\item
\hyperref[updating-liferay-maven-artifact-dependencies]{Updating
Liferay Maven artifact dependencies}
\end{itemize}
For more information on using Maven with 7.0, see the
\href{/docs/7-2/reference/-/knowledge_base/r/maven}{Maven} section.
Liferay also offers a Maven development environment tailored
specifically for 7.0 development. Learn more about this in the
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace} section.
You'll start off by upgrading your Maven environment's Liferay Maven
plugins.
\section{Upgrading to New 7.0 Maven
Plugins}\label{upgrading-to-new-7.0-maven-plugins}
The biggest change for your project's build plugins is the removal of
the \texttt{liferay-maven-plugin}. Liferay now provides several
individual Maven plugins that accomplish specific tasks. For example,
you can configure Maven plugins for Liferay's CSS Builder, Service
Builder, Theme Builder, etc. With smaller plugins available to
accomplish specific tasks in your project, you no longer have to rely on
one large plugin that provides many things you may not want.
For example, suppose your Liferay Portal 6.2 project was using the
\texttt{liferay-maven-plugin} for Liferay CSS Builder only. First, you
should remove the legacy \texttt{liferay-maven-plugin} plugin dependency
from your project's \texttt{pom.xml} file:
\begin{verbatim}
com.liferay.maven.plugins
liferay-maven-plugin
${liferay.version}
${liferay.auto.deploy.dir}
${liferay.version}
portlet
\end{verbatim}
Then, add the CSS Builder plugin dependency to your project's
\texttt{pom.xml} file:
\begin{verbatim}
com.liferay
com.liferay.css.builder
2.1.3
default-build
generate-sources
build
src/main/webapp
\end{verbatim}
Some common Liferay Maven plugins are listed below, with their
corresponding artifact IDs and articles explaining how to configure
them:
\textbf{Common Liferay Maven Plugins}
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2083}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.4583}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Name
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Artifact ID
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Tutorial
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Bundle Support &
\href{https://search.maven.org/search?q=com.liferay.portal.tools.bundle.support}{com.liferay.portal.tools.bundle.support}
&
\href{/docs/7-2/reference/-/knowledge_base/r/bundle-support-plugin}{Bundle
Support Plugin} \\
CSS Builder &
\href{https://search.maven.org/search?q=com.liferay.css.builder}{com.liferay.css.builder}
& \href{/docs/7-2/reference/-/knowledge_base/r/css-builder-plugin}{CSS
Builder Plugin} \\
DB Support &
\href{https://search.maven.org/search?q=com.liferay.portal.tools.db.support}{com.liferay.portal.tools.db.support}
& \href{/docs/7-2/reference/-/knowledge_base/r/db-support-plugin}{DB
Support Plugin} \\
Deployment Helper &
\href{https://search.maven.org/search?q=com.liferay.deployment.helper}{com.liferay.deployment.helper}
&
\href{/docs/7-2/reference/-/knowledge_base/r/deployment-helper-plugin}{Deployment
Helper Plugin} \\
Javadoc Formatter &
\href{https://search.maven.org/search?q=com.liferay.javadoc.formatter}{com.liferay.javadoc.formatter}
&
\href{/docs/7-2/reference/-/knowledge_base/r/javadoc-formatter-plugin}{Javadoc
Formatter Plugin} \\
Lang Builder &
\href{https://search.maven.org/search?q=com.liferay.lang.builder}{com.liferay.lang.builder}
& \href{/docs/7-2/reference/-/knowledge_base/r/lang-builder-plugin}{Lang
Builder Plugin} \\
REST Builder &
\href{https://search.maven.org/search?q=com.liferay.portal.tools.rest.builder}{com.liferay.portal.tools.rest.builder}
& \href{/docs/7-2/reference/-/knowledge_base/r/rest-builder-plugin}{REST
Builder Plugin} \\
Service Builder &
\href{https://search.maven.org/search?q=com.liferay.portal.tools.service.builder}{com.liferay.portal.tools.service.builder}
&
\href{/docs/7-2/reference/-/knowledge_base/r/service-builder-plugin}{Service
Builder Plugin} \\
Source Formatter &
\href{https://search.maven.org/search?q=com.liferay.source.formatter}{com.liferay.source.formatter}
&
\href{/docs/7-2/reference/-/knowledge_base/r/source-formatter-plugin}{Source
Formatter Plugin} \\
Theme Builder &
\href{https://search.maven.org/search?q=com.liferay.portal.tools.theme.builder}{com.liferay.portal.tools.theme.builder}
&
\href{/docs/7-2/reference/-/knowledge_base/r/theme-builder-plugin}{Theme
Builder Plugin} \\
TLD Formatter &
\href{https://search.maven.org/search?q=com.liferay.tld.formatter}{com.liferay.tld.formatter}
& \href{/docs/7-2/reference/-/knowledge_base/r/tld-formatter-plugin}{TLD
Formatter Plugin} \\
WSDD Builder &
\href{https://search.maven.org/search?q=com.liferay.portal.tools.wsdd.builder}{com.liferay.portal.tools.wsdd.builder}
& \href{/docs/7-2/reference/-/knowledge_base/r/wsdd-builder-plugin}{WSDD
Builder Plugin} \\
XML Formatter &
\href{https://search.maven.org/search?q=com.liferay.xml.formatter}{com.liferay.xml.formatter}
& \href{/docs/7-2/reference/-/knowledge_base/r/xml-formatter-plugin}{XML
Formatter Plugin} \\
\end{longtable}
\noindent\hrulefill
\textbf{Note:} When upgrading to a Liferay Workspace built with Maven,
many of these plugins are applied to the workspace by default.
\noindent\hrulefill
In Liferay Portal 6.2, you were also required to specify all your app
server configuration settings. For example, your parent POM probably
contained settings similar to these:
\begin{verbatim}
E:\liferay-portal-6.2.0-ce-ga1\tomcat-7.0.42\webapps
E:\liferay-portal-6.2.0-ce-ga1\tomcat-7.0.42\lib\ext
E:\liferay-portal-6.2.0-ce-ga1\tomcat-7.0.42\webapps\root
E:\liferay-portal-6.2.0-ce-ga1\deploy
6.2.0
6.2.0
\end{verbatim}
This is no longer required in 7.0 because Liferay's Maven tools no
longer rely on your Liferay DXP installation's specific versions. You
should remove them from your POM file.
Awesome! You've learned about the new Maven plugins available to you for
7.0 development. Next, you'll learn about updating your Liferay Maven
artifacts.
\section{Updating Liferay Maven Artifact
Dependencies}\label{updating-liferay-maven-artifact-dependencies}
Many Liferay Portal 6.2 artifact dependencies you were using have
changed in 7.0. See the table below for popular Liferay Maven artifacts
that have changed:
\begin{longtable}[]{@{}ll@{}}
\toprule\noalign{}
Liferay Portal 6.2 Artifact ID & 7.0 Artifact ID \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{portal-service} & \texttt{com.liferay.portal.kernel} \\
\texttt{util-bridges} & \texttt{com.liferay.util.bridges} \\
\texttt{util-java} & \texttt{com.liferay.util.java} \\
\texttt{util-slf4j} & \texttt{com.liferay.util.slf4j} \\
\texttt{util-taglib} & \texttt{com.liferay.util.taglib} \\
\end{longtable}
For more information on resolving dependencies in 7.0, see the
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Resolving
a Plugin's Dependencies} article.
Of course, you must also update the artifacts you're referencing for
your projects. If you're using the Central Repository to install Liferay
Maven artifacts, you won't need to do anything more than update the
artifacts in your POMs. If, however, you're working behind a proxy or
don't have Internet access, you must update your company-shared or local
repository with the latest 7.0 Maven artifacts.
With these updates, you can easily upgrade your Liferay Maven build so
you can begin developing projects for 7.0.
\chapter{Theme Generator}\label{theme-generator}
The Liferay Theme Generator generates themes for Liferay DXP. It is just
one of Liferay JS Theme Toolkit's
\href{https://github.com/liferay/liferay-themes-sdk/tree/master/packages}{tools}.
A couple versions of the Liferay Theme Generator are available. The
version you must install depends on the version of Liferay DXP you're
developing on. The required versions are listed in the table below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.1717}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.3333}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.4949}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Liferay Version
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Liferay Theme Generator Version
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Command
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
6.2 & 7.x.x &
\texttt{npm\ install\ -g\ generator-liferay-theme@\^{}7.x.x} \\
7.0 & 7.x.x or 8.x.x & Same as above or below \\
7.1 & 8.x.x &
\texttt{npm\ install\ -g\ generator-liferay-theme@\^{}8.x.x} \\
7.2 & 9.x.x &
\texttt{npm\ install\ -g\ generator-liferay-theme@\^{}9.x.x} \\
\end{longtable}
\noindent\hrulefill
See
\href{https://learn.liferay.com/w/dxp/building-applications/tooling/reference/node-version-information\#version-compatibility-matrix}{Version
Compatibility Matrix} for more information.
\section{Sub-generators}\label{sub-generators}
The Liferay Theme Generator includes the sub-generators listed in the
table below:
\noindent\hrulefill
\begin{longtable}[]{@{}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.1376}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.2661}}
>{\raggedright\arraybackslash}p{(\columnwidth - 4\tabcolsep) * \real{0.5963}}@{}}
\toprule\noalign{}
\begin{minipage}[b]{\linewidth}\raggedright
Sub-generator
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Command to run
\end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
Description
\end{minipage} \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
Layouts & \texttt{yo\ liferay-theme:layout} & Generate layout templates
with an interactive VIM. \\
Themelets & \texttt{yo\ liferay-theme:themelet} & Create small,
reusable, pieces of CSS and HTML for your themes. \\
\end{longtable}
\noindent\hrulefill
\section{Layouts Sub-generator}\label{layouts-sub-generator}
The Layouts sub-generator provides the controls to create a layout
template that meets your needs. You can add and remove rows and columns
on-the-fly as you require. See
\href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Generating
Layout Templates} for more information.
\section{Themelets Sub-generator}\label{themelets-sub-generator}
Themelets are small, extendable, and reusable pieces of code. They only
require the CSS and/or JavaScript files that you want to add to your
theme. This modular approach reduces the need for duplicated code across
themes and makes it easy for developers to share code snippets with
other developers. Themelets are applicable for changes both small and
large, from modifying the appearance of an individual piece of UI to
adding new features. If there is something you have to manually code for
every theme you create, it's a good candidate for a themelet. See
\href{/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator}{Generating
Themelets} for more information.
While you can create themes using the tools you prefer, the Liferay
Theme Generator is designed with theme development for Liferay DXP in
mind. Its toolset and features help streamline theme development.
\chapter{Installing the Theme Generator and Creating a
Theme}\label{installing-the-theme-generator-and-creating-a-theme}
The steps below show how to install the Liferay Theme Generator and
generate a theme.
\begin{figure}
\centering
\includegraphics{./images/theme-generator-theme-example.png}
\caption{The tools are in your hands to create any theme you can
imagine.}
\end{figure}
Your first step in generating a theme is installing
\href{http://nodejs.org/}{NodeJS} (along with Node Package Manager(npm))
if it's not already installed. We recommend installing
\href{https://nodejs.org/download/release/v10.15.1/}{v10.15.1}, which is
the version Liferay Portal 7.2 supports (See the
\href{https://learn.liferay.com/w/dxp/building-applications/tooling/reference/node-version-information\#version-compatibility-matrix}{compatibility
matrix}). Once NodeJS is installed and you've
\href{/docs/7-2/reference/-/knowledge_base/r/setting-up-your-npm-environment}{set
up your npm environment}, you can follow these steps to install the
Liferay Theme Generator and generate a theme:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Use npm to install the \href{http://yeoman.io/}{Yeoman} dependency:
\begin{verbatim}
npm install -g yo
\end{verbatim}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Gulp is included as a local dependency in generated themes, so you
are not required to install it. It can be accessed by running
`node_modules\.bin\gulp` followed by the Gulp task from a generated theme's
root folder.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Install the Liferay Theme Generator with the command below:
\begin{verbatim}
npm install -g generator-liferay-theme
\end{verbatim}
If you're on Windows, follow the instructions in step 3 to install
Sass, otherwise you can skip to step 4.
\item
The generator uses node-sass. If you're on Windows, you must also
install
\href{https://github.com/nodejs/node-gyp\#installation}{node-gyp and
Python}.
\item
Run the generator and follow the prompts to create your theme:
\begin{verbatim}
yo liferay-theme
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/theme-generator-theme-prompt.png}
\caption{You can generate a theme by answering just a few
configuration questions.}
\end{figure}
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Since Liferay DXP Fix Pack 2 and Liferay Portal 7.2 CE GA2, Font
Awesome is available globally as a system setting, which is enabled by
default. If you're using Font Awesome icons in your theme, answer yes (y)
to the Font Awesome question to include Font Awesome imports in your
theme. This ensures that your icons won't break if a Site Administrator
disables the global setting.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
Navigate to your theme folder and run \texttt{gulp\ deploy} to deploy
your new theme to the server.
\end{enumerate}
Now you have a powerful theme development tool at your disposal. The sky
is the limit!
\chapter{Generating Layout Templates with the Theme
Generator}\label{generating-layout-templates-with-the-theme-generator}
This article shows how to use the Liferay Theme Generator's Layouts
sub-generator to create a layout template.
\begin{figure}
\centering
\includegraphics{./images/layout-template-1-2-1-columns.png}
\caption{The \emph{1-2-1 Columns} page layout creates a nice flow for
your content.}
\end{figure}
Your first step in creating a layout template with the Liferay Theme
Generator's Layouts sub-generator is installing the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator} if it's not already installed. Once the generator is
installed, you can follow these steps to create a layout template:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Open the Command Line and navigate to the folder where you want to
create your layout template.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Run the Layouts sub-generator from the theme's root folder to
bundle it with the theme. This adds the layout template to the theme's
`src/layouttpl/custom` folder. This **only works** for generated themes.
\end{verbatim}
\noindent\hrulefill
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\item
Run The Layouts sub-generator with the command below, and use the
options listed below to create your layout:
\begin{verbatim}
yo liferay-theme:layout
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/layout-column-widths.png}
\caption{You must specify the width for each column in the row.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/layout-prompt.png}
\caption{The Layouts sub-generator automates the layout creation
process.}
\end{figure}
\begin{itemize}
\item
\textbf{Add a row:} Adds a row below the last row.
\item
\textbf{Insert row:} Displays a vi to insert your row. Use your
arrow keys to choose where to insert your row, highlighted in blue,
then press Enter to insert the row.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/insert-row.png}
\caption{Rows can be inserted using the layout vi.}
\end{figure}
\begin{itemize}
\tightlist
\item
\textbf{Remove row:} Displays a vi to remove your row. Use your
arrow keys to select the row you want to remove, highlighted in red,
then press Enter to remove the row.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/remove-row.png}
\caption{Rows are removed using the layout vi.}
\end{figure}
\begin{itemize}
\tightlist
\item
\textbf{Finish Layout:} Complete the layout template.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/finish-layout.png}
\caption{Select the \emph{Finish layout} option to complete your
design.}
\end{figure}
\item
Run \texttt{gulp\ deploy} to deploy your layout template to the server
you specified, or deploy your theme if the layout is
\href{/docs/7-2/frameworks/-/knowledge_base/f/including-layout-templates-with-a-theme}{bundled
with it}.
\end{enumerate}
\noindent\hrulefill
\begin{verbatim}
**Note:** Gulp is included as a local dependency of the generator, so you
are not required to install it. It can be accessed by running
`node_modules\.bin\gulp` followed by the Gulp task from a generated theme's
root folder.
\end{verbatim}
\noindent\hrulefill
Awesome! You just created a layout template with the Theme Generator's
Layouts sub-generator. Your layout template project should have a file
structure similar to the one below:
\begin{itemize}
\tightlist
\item
\texttt{my-liferay-layout-layouttpl/}
\begin{itemize}
\tightlist
\item
\texttt{docroot/}
\begin{itemize}
\tightlist
\item
\texttt{WEB-INF/}
\begin{itemize}
\tightlist
\item
\texttt{liferay-layout-templates.xml}
\item
\texttt{liferay-plugin-package.properties}
\end{itemize}
\item
\texttt{my\_liferay\_layout.png}
\item
\texttt{my\_liferay\_layout.tpl}
\end{itemize}
\item
\texttt{node\_modules/}
\begin{itemize}
\tightlist
\item
(lots of packages)
\end{itemize}
\item
\texttt{gulpfile.js}
\item
\texttt{liferay-plugin.json}
\item
\texttt{package-lock.json}
\item
\texttt{package.json}
\end{itemize}
\end{itemize}
\chapter{Generating Themelets with the Theme
Generator}\label{generating-themelets-with-the-theme-generator}
This steps below show how to use the Liferay Theme Generator's Themelets
sub-generator to create a themelet.
\begin{figure}
\centering
\includegraphics{./images/product-menu-animation-themelet.png}
\caption{Themelets can be used to modify one aspect of the UI, that you
can then reuse in your other themes.}
\end{figure}
Your first step in creating a themelet is installing the
\href{/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme}{Liferay
Theme Generator} if it's not already installed. Once the generator is
installed, you can follow these steps to create a themelet:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the Command Line and navigate to the folder you want to create
your themelet in.
\item
Run \texttt{yo\ liferay-theme:themelet} and follow the prompts to
generate the themelet.
\begin{figure}
\centering
\includegraphics{./images/themelet-prompt.png}
\caption{The Themelet sub-generator automates the themelet creation
process, making it quick and easy.}
\end{figure}
The generated themelet contains a \texttt{package.json} file with
configuration information and a \texttt{src/css} folder that contains
a \texttt{\_custom.scss} file. Just like a theme, add your CSS changes
to the \texttt{src/css} folder, and add your JavaScript changes to the
\texttt{src/js} folder.
\item
To use your themelet, you must install it globally first. This makes
the themelet visible to the generator. To install your themelet
globally, navigate into its root folder and run \texttt{npm\ link}.
Note, you may need to run the command using \texttt{sudo\ npm\ link}.
This creates a globally-installed symbolic link for the themelet in
your npm packages folder. Now your themelet is available to install in
your themes.
\end{enumerate}
Awesome! You just created a themelet with the Theme Generator's
Themelets sub-generator.
\chapter{Liferay Upgrade Planner}\label{liferay-upgrade-planner}
The Liferay Upgrade Planner provides an automated way to adapt your
installation's data and legacy plugins to your desired Liferay DXP
upgrade version. We recommend leveraging this tool for any of the
following upgrades:
\begin{itemize}
\tightlist
\item
Liferay Portal 6.2 → Liferay DXP 7.0, 7.1, or 7.2
\item
Liferay DXP 7.0 → Liferay DXP 7.1 or 7.2
\item
Liferay DXP 7.1 → Liferay DXP 7.2
\end{itemize}
For step-by-step instructions for following the two upgrade paths, see
the following documentation:
\begin{itemize}
\tightlist
\item
\href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver}{Data
Upgrade}
\item
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{Code
Upgrade}
\end{itemize}
The Upgrade Planner is provided in
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay
Dev Studio} (versions 3.6+). Here's what the Upgrade Planner does:
\begin{itemize}
\tightlist
\item
Updates your development environment.
\item
Identifies code affected by the API changes.
\item
Describes each API change related to the code.
\item
Suggests how to adapt the code.
\item
Provides options, in some cases, to adapt code automatically.
\item
Transfers database and server data to your new environment.
\end{itemize}
Even if you prefer tools other than Dev Studio (which is based on
Eclipse), you should upgrade your data and legacy plugins using the
Upgrade Planner first--you can use your favorite tools afterward.
To start the Upgrade Planner in Dev Studio, do this:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to \emph{Project} → \emph{New Liferay Upgrade Plan\ldots{}}.
\item
In the New Liferay Upgrade Plan wizard, assign your plan a name and
choose an upgrade plan outline. The data and code upgrade processes
are separate, so you must step through each process independently.
\item
Choose your current Liferay version and the new version you're
upgrading to.
\item
If you chose to complete a code upgrade, you must also select the
folder where your legacy plugins reside (e.g., Plugins SDK for Liferay
6.2 projects).
\item
Click \emph{Finish}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/upgrade-plan-wizard.png}
\caption{Configure your upgrade plan before beginning the upgrade
process.}
\end{figure}
Switch to the new Liferay Upgrade Planner perspective (prompted
automatically). You're now offered several windows in the UI:
\begin{itemize}
\tightlist
\item
\emph{Project Explorer:} displays your legacy plugin environment and
new development environment. It also displays your
\href{/docs/7-2/tutorials/-/knowledge_base/t/fixing-upgrade-problems}{upgrade
problems} that are detected during the \emph{Fix Upgrade Problems}
step.
\item
\emph{Liferay Upgrade Plan:} outlines the upgrade plan's steps and
step summaries.
\item
\emph{Liferay Upgrade Plan Info:} shows official documentation that
describes the upgrade step.
\end{itemize}
To progress through your upgrade plan, click the steps outlined in the
Liferay Upgrade Plan window. Each step can have several options:
\begin{itemize}
\tightlist
\item
\emph{Click to preview:} previews what an automated step will perform.
\item
\emph{Click to perform:} executes an automated process provided with
the step. This is only offered for steps where the Upgrade Planner can
assist.
\item
\emph{Click when complete:} marks the step as complete. This is only
offered when the Upgrade Planner cannot provide automated assistance
and, instead, only offers documentation to assist in completing the
step manually.
\item
\emph{Restart:} marks a completed step as unfinished. The step is
performed again if automation is involved.
\item
\emph{Skip:} skips the step and jumps to the next step in the outline.
\end{itemize}
\begin{figure}
\centering
\includegraphics{./images/preview-upgrade-planner-changes.png}
\caption{You can preview the Upgrade Planner's automated updates before
you perform them.}
\end{figure}
Great! You now have a good understanding of the Liferay Upgrade
Planner's UI and how to get started. Visit the
\href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver}{Data
Upgrade} and
\href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{Code
Upgrade} sections for more information on those upgrade processes.
\chapter{Using the Upgrade Planner with Proxy
Requirements}\label{using-the-upgrade-planner-with-proxy-requirements}
If you have proxy server requirements and want to configure your http(s)
proxy\\
to work with the Liferay Upgrade Planner, follow the instructions below.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In Dev Studio's \texttt{DeveloperStudio.ini}/\texttt{eclipse.ini}
file, add the following parameters:
\begin{verbatim}
-Djdk.http.auth.proxying.disabledSchemes=
-Djdk.http.auth.tunneling.disabledSchemes=
\end{verbatim}
\item
Launch Dev Studio.
\item
Go to \emph{Window} → \emph{Preferences} → \emph{General} →
\emph{Network Connections}.
\item
Set the \emph{Active Provider} drop-down selector to \emph{Manual}.
\item
Under \emph{Proxy entries}, configure both proxy HTTP and HTTPS by
clicking the field and selecting the \emph{Edit} button.
\begin{figure}
\centering
\includegraphics{./images/upgrade-planner-proxy.png}
\caption{You can configure your proxy settings in Dev Studio's Network
Connections menu.}
\end{figure}
\item
For each schema (HTTP and HTTPS), enter your proxy server's host,
port, and authentication settings (if necessary). Do not leave
whitespace at the end of your proxy host or port settings.
\item
Once you've configured your proxy entry, click \emph{Apply and Close}.
\end{enumerate}
Awesome! You've successfully configured the Upgrade Planner's proxy
settings!
\chapter{Web Experience Management
Reference}\label{web-experience-management-reference}
Browse this section's reference articles for additional information on
the Web Experience Management framework.
\chapter{Fragment Specific Tags}\label{fragment-specific-tags}
There are Liferay-specific tags for creating editable, text, image, and
link fields, and for embedding widgets.
\section{Making Text Editable}\label{making-text-editable}
You can make text of a fragment editable by enclosing it in an
\texttt{\textless{}lfr-editable\textgreater{}} tag like this:
\begin{verbatim}
This is editable text!
\end{verbatim}
If you need formatting options like text or color styles, use
\texttt{rich-text}:
\begin{verbatim}
This is editable text that I can make bold or italic!
\end{verbatim}
The \texttt{lfr-editable} tag doesn't render without a unique
\texttt{id}.
\noindent\hrulefill
\textbf{Note:} If you want to make text inside an HTML element editable,
you must use the \texttt{rich-text} type. The \texttt{text} type strips
HTML formatting out of the text before rendering.
\noindent\hrulefill
\section{Making Images Editable}\label{making-images-editable}
Images use the same \texttt{\textless{}lfr-editable\textgreater{}} tag
as text, but with the \texttt{image} type, like this:
\begin{verbatim}
\end{verbatim}
After you add the \texttt{lfr-editable} tag with the type \texttt{image}
to a Fragment, when you add that Fragment to a page, you can then click
on the editable image to select an image or configure content mapping
for the image.
\begin{figure}
\centering
\includegraphics{./images/fragment-image-editor.png}
\caption{You have several options for defining an image on a Content
Page.}
\end{figure}
Most images can be handled like this, but to add an editable background
image you must add an additional property to set the background image
ID, \texttt{data-lfr-background-image-id}. The background image ID is
set in the main \texttt{div} for the Fragment and is the same as your
editable image ID.
\begin{verbatim}
\end{verbatim}
Content mapping connects editable fields in your Fragment with fields
from an Asset type like Web Content or Blogs. For example, you can map
an image field to display a preview image for a Web Content Article. For
more information on mapping fields, see
\href{/docs/7-2/user/-/knowledge_base/u/content-page-elements\#editable-elements}{Editable
Elements}.
\section{Creating Editable Links}\label{creating-editable-links}
There is also a specific syntax for creating editable link elements:
\begin{verbatim}
Link text goes here
\end{verbatim}
Marketers can edit the link text, target URL, and basic link
styling---primary button, secondary button, link.
\begin{figure}
\centering
\includegraphics{./images/fragment-link-editor.png}
\caption{You have several options for defining a link's appearance and
behavior.}
\end{figure}
For more information on editable links, see
\href{/docs/7-2/user/-/knowledge_base/u/content-page-elements\#editable-links}{Editable
Links}.
\section{Including Widgets Within A
Fragment}\label{including-widgets-within-a-fragment}
To include a widget, you must know its registered name. For example, the
Site Navigation Menu portlet is registered as \texttt{nav}. Each
registered portlet has an \texttt{lfr-widget-{[}name{]}} tag that's used
to embed it. For example: the Navigation Menu tag is
\texttt{\textless{}lfr-widget-nav\ /\textgreater{}}. You could embed it
in a block like this:
\begin{verbatim}
\end{verbatim}
These are the widgets that can be embedded and their accompanying tags:
\noindent\hrulefill
\begin{longtable}[]{@{}ll@{}}
\toprule\noalign{}
Widget Name & Tag \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
DDL Display &
\texttt{\textless{}lfr-widget-dynamic-data-list\textgreater{}} \\
Form & \texttt{\textless{}lfr-widget-form\textgreater{}} \\
Asset Publisher &
\texttt{\textless{}lfr-widget-asset-list\textgreater{}} \\
Breadcrumb & \texttt{\textless{}lfr-widget-breadcrumb\textgreater{}} \\
Categories Navigation &
\texttt{\textless{}lfr-widget-categories-nav\textgreater{}} \\
Flash & \texttt{\textless{}lfr-widget-flash\textgreater{}} \\
Media Gallery &
\texttt{\textless{}lfr-widget-media-gallery\textgreater{}} \\
Navigation Menu & \texttt{\textless{}lfr-widget-nav\textgreater{}} \\
Polls Display & \texttt{\textless{}lfr-widget-polls\textgreater{}} \\
Related Assets &
\texttt{\textless{}lfr-widget-related-assets\textgreater{}} \\
Site Map & \texttt{\textless{}lfr-widget-site-map\textgreater{}} \\
Tag Cloud & \texttt{\textless{}lfr-widget-tag-cloud\textgreater{}} \\
Tags Navigation &
\texttt{\textless{}lfr-widget-tags-nav\textgreater{}} \\
Web Content Display &
\texttt{\textless{}lfr-widget-web-content\textgreater{}} \\
RSS Publisher (Deprecated) &
\texttt{\textless{}lfr-widget-rss\textgreater{}} \\
Iframe & \texttt{\textless{}lfr-widget-iframe\textgreater{}} \\
\end{longtable}
\noindent\hrulefill
\section{Enabling Embedding for Your
Widget}\label{enabling-embedding-for-your-widget}
If you have a custom widget that you want to embed in a fragment, you
can configure that widget to be embeddable. To embed your widget, it
must be an OSGi Component. Inside the \texttt{@Component} annotation for
the portlet class you want to embed, add this property:
\begin{verbatim}
com.liferay.fragment.entry.processor.portlet.alias=app-name
\end{verbatim}
When you deploy your widget, it's available to add. The name you specify
in the property must be appended to the \texttt{lfr-widget} tag like
this:
\begin{verbatim}
\end{verbatim}
\noindent\hrulefill
\textbf{Note:} According to the W3C HTML standards, custom elements
cannot be self closing. Therefore, even though you cannot add anything
between the opening and closing
\texttt{\textless{}lfr-widget...\textgreater{}} tags, you cannot use the
self closing notation for the tag.
\chapter{Fragment Configuration
Types}\label{fragment-configuration-types}
There are four configurable Fragment types available to implement:
\begin{itemize}
\tightlist
\item
\texttt{checkbox}
\item
\texttt{colorPalette}
\item
\texttt{select}
\item
\texttt{text}
\end{itemize}
For more information on how to make a Fragment configurable, see
\href{/docs/7-2/frameworks/-/knowledge_base/f/making-a-fragment-configurable}{Making
a Fragment Configurable}.
In this article, you'll explore JSON examples of each Fragment type.
\section{Checkbox Configuration}\label{checkbox-configuration}
The following JSON configuration creates a checkbox you can implement
for cases where a boolean value selection is necessary:
\begin{verbatim}
{
"fieldSets": [
{
"fields": [
{
"name": "hideBody",
"label": "Hide Body",
"description": "hide-body",
"type": "checkbox",
"defaultValue": false
}
...
]
}
]
}
\end{verbatim}
\begin{figure}
\centering
\includegraphics{./images/fragment-config-checkbox.png}
\caption{The checkbox configuration is useful when a boolean selection
is necessary.}
\end{figure}
\section{Color Palette Configuration}\label{color-palette-configuration}
The following JSON configuration creates a color selector you can
implement for cases where you must select a color:
\begin{verbatim}
{
"fieldSets": [
{
"fields": [
{
"name": "textColor",
"label": "Text color",
"type": "colorPalette",
"dataType": "object",
"defaultValue": {
"cssClass": "white",
"rgbValue": "rgb(255,255,255)"
}
}
...
]
}
]
}
\end{verbatim}
The \texttt{colorPalette} type stores an object with two values:
\texttt{cssClass} and \texttt{rgbValue}.
For example, if you implement the snippet above, you can use it in the
FreeMarker context like this:
\begin{verbatim}
Example
\end{verbatim}
If you were to choose the color white, the \texttt{h3} tag heading would
have the class \texttt{text-white\textquotesingle{}}.
\begin{figure}
\centering
\includegraphics{./images/fragment-config-colorpalette.png}
\caption{The \texttt{colorPalette} configuration is useful when a color
selection is necessary.}
\end{figure}
\section{Select Configuration}\label{select-configuration}
The following JSON configuration creates a selector you can implement
for cases where you must select a predefined option:
\begin{verbatim}
{
"fieldSets": [
{
"fields": [
{
"name": "numberOfFeatures",
"label": "Number Of Features",
"description": "number-of-features",
"type": "select",
"dataType": "int",
"typeOptions": {
"validValues": [
{"value": "1"},
{"value": "2"},
{"value": "3"}
]
},
"defaultValue": "3"
}
...
]
}
]
}
\end{verbatim}
Configuration values inserted into the FreeMarker context honor the
defined \texttt{datatype} value specified in the JSON file.
\begin{figure}
\centering
\includegraphics{./images/fragment-config-select.png}
\caption{The \texttt{select} configuration is useful when an option
choice is necessary.}
\end{figure}
\section{Text Configuration}\label{text-configuration}
The following JSON configuration creates an input text field you can
implement for cases where you must manually input a text option:
\begin{verbatim}
{
"fieldSets": [
{
"fields": [
{
"name": "buttonText",
"label": "Button Text",
"description": "button-text",
"type": "text",
"typeOptions": {
"placeholder": "Placeholder"
},
"dataType": "string",
"defaultValue": "Go Somewhere"
}
...
]
}
]
}
\end{verbatim}
Configuration values inserted into the FreeMarker context honor the
defined \texttt{datatype} value specified in the JSON file.
\begin{figure}
\centering
\includegraphics{./images/fragment-config-text.png}
\caption{The \texttt{text} configuration is useful when an input text
option is necessary.}
\end{figure}
\chapter{Escaping Fragment Configuration Text
Values}\label{escaping-fragment-configuration-text-values}
When you define text configuration and other options for a Fragment,
Fragment developers can declare any text value they want. With this
freedom comes risk; malicious code could be inserted into the text
field, wreaking havoc for other users of the Fragment.
In this article, you'll learn how to escape Fragment text values so
Fragment authors are protected from XSS attacks.
\section{Escaping Values in
HTML/FreeMarker}\label{escaping-values-in-htmlfreemarker}
You must take special care when adding a text value in your Fragment's
HTML. For example, if a user includes malicious code within
\texttt{\textless{}script\textgreater{}} tags, it runs when the page is
rendered.
To solve this problem, a utility is available in the FreeMarker context
via the \texttt{htmlUtil} class.
For generic cases, an \texttt{escape} method is available:
\begin{verbatim}
${htmlUtil.escape(configuration.text)}"
\end{verbatim}
This escapes your Fragment configuration's \texttt{text} field,
preventing malicious code from affecting Fragment authors.
For more information on escaping methods, see the
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/HtmlUtil.html}{HtmlUtil}
class.
\section{Escaping Values in
JavaScript}\label{escaping-values-in-javascript}
When including JavaScript in your Fragment, you must be aware of
potential attack vectors, like setting an attribute or appending HTML
children. To prevent these attacks, you should use the
\texttt{Liferay.Util.escapeHTML} function. You can call it from your
Fragment's JavaScript like this:
\begin{verbatim}
function (fragmentElement, configuration) {
const escapedValue = Liferay.Util.escapeHTML(configuration.text)
}
\end{verbatim}
This escapes your Fragment configuration's \texttt{text} field,
preventing malicious code in your Fragment's JavaScript code.
\chapter{Asset Display Page Taglib}\label{asset-display-page-taglib}
Once you have created an Asset Display Page associated with your Asset
type, you can add the option to select an Asset Display Page in the form
where you create the Asset. The
\texttt{\textless{}liferay-asset:select-asset-display-page\ /\textgreater{}}
taglib renders a form field for selecting an Asset Display Page for your
asset.
There are three options when selecting a display page:
\begin{itemize}
\item
The default display page for the asset type if one has been
configured.
\item
Any other selectable display page.
\item
None
\end{itemize}
If you select no default display page, a display page is not defined.
\section{Display Page Attributes}\label{display-page-attributes}
When you use the display page selector taglib, you can define the
following attributes:
\texttt{classNameId\ (long)} (required): a class name ID of the asset
type to select an asset display page for.
\texttt{classPK\ (long)}: a primary key of the asset entry to select an
asset display page for.
\texttt{classTypeId\ (long)}: a class type ID of the asset type to
select an asset display page for.
\texttt{eventName\ (String)}: event name which fires when a user selects
a display page using the item selector.
\texttt{groupId\ (long)} (required): the entity's group ID to select an
asset display page for.
\texttt{showPortletLayouts\ (boolean)}: allow selection of pages that
have Asset Publisher configured as a default Asset Publisher for the
page.
The attribute \texttt{showPortletLayout} provides backwards
compatibility for display pages created for Journal Articles in older
versions. When \texttt{showPortletLayouts} is set to true, you can
select any public or private pages with an Asset Publisher widget on it
configured as the \emph{Default Asset Publisher for the page}.
When submitting a form with the taglib, it populates the request with
the following parameters:
\texttt{displayPageType\ (int)}: 1 = Default, 2 = Specific, 3 = None.
\texttt{assetDisplayPageId\ (long)}: ID of selected Asset Display Page.
\texttt{layoutUuid}: Layout UUID in case of a portlet page with default
Asset Publisher.
\chapter{Crafting XML Workflow
Definitions}\label{crafting-xml-workflow-definitions}
You don't need a fancy visual designer to build workflows. To be clear,
Kaleo Designer may make you a faster workflow designer through its
graphical interface. If you plan to build lots of workflow processes, a
Digital Enterprise subscription gets you access to Kaleo Designer. But
with a little copy and paste from existing workflows and a little
handcrafted XML, you can build any workflow and attain workflow
wizard-hood in the process. Follow this set of tutorials to learn what
elements you can put into your definitions.
\section{Existing Workflow
Definitions}\label{existing-workflow-definitions}
Only one workflow definition is installed by default: Single Approver.
Several more, however, are embedded in the source code of your Liferay
DXP installation. If you're comfortable extracting the XML files from a
JAR file embedded in an LPKG file, you're welcome to follow the steps
below to obtain the workflow definitions.
To extract the definitions for yourself,
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Navigate to
\begin{verbatim}
[Liferay Home]/osgi/marketplace
\end{verbatim}
\item
Using an archive manager, open the LPKG
\begin{verbatim}
Liferay CE Forms and Workflow.lpkg
\end{verbatim}
\item
Open the JAR file named
\begin{verbatim}
com.liferay.portal.workflow.kaleo.runtime.impl-[version].jar
\end{verbatim}
\item
In the JAR file, navigate to
\begin{verbatim}
META-INF/definitions/
\end{verbatim}
\item
Extract the four XML workflow definition files. These definitions
provide good reference material for many of the workflow features and
elements described in these articles. In fact, most of the XML
snippets you see here are lifted directly from these definitions.
\end{enumerate}
\section{Schema}\label{schema}
The XML structure of a workflow definition is defined in an XSD file:
\begin{verbatim}
liferay-worklow-definition-7_2_0.xsd
\end{verbatim}
Declare the schema at the top of the workflow definition file:
\begin{verbatim}
\end{verbatim}
To read 464 lines of beautifully formatted XML that defines how to write
more XML (it's practically poetic), check out the XSD
\href{https://www.liferay.com/dtd/liferay-workflow-definition_7_2_0.xsd}{here}.
Otherwise, move on to entering the definition's metadata.
\section{Metadata}\label{metadata}
Give the definition a name, description, and version:
\begin{verbatim}
Category Specific Approval
A single approver can approve a workflow content.
1
\end{verbatim}
All these tags are optional. If present the first time a definition is
saved, the \texttt{\textless{}name\textgreater{}} tag serves as a unique
identifier for the definition. If not specified (or added sometime after
the first save), a random unique name is generated and used to identify
the workflow.
Once the schema and metadata are in place, it's time to turn up the
funky beats and get into the flow (the workflow). Learn about workflow
nodes in the next article.
\chapter{Workflow Definition Nodes}\label{workflow-definition-nodes}
After your definition's schema and metadata are in place, begin defining
the process. \emph{Node} elements, with their sub-elements, are
fundamental building blocks making up workflow definitions.
\section{State Nodes}\label{state-nodes}
State nodes don't require user input. The workflow does whatever is
specified in the state node's \texttt{actions} tag (a notification
and/or a custom script), and then moves to the provided transition.
Workflows start and end with a state. The initial state node often only
contains a transition:
\begin{verbatim}
created
true
Determine Branch
determine-branch
true
\end{verbatim}
If a notification or script is required in your state node, use an
\texttt{actions} tag. Here's an \texttt{action} element containing a
Groovy script. This is found in many terminal state nodes and marks the
asset as approved in the workflow.
\begin{verbatim}
Approve
Approve
groovy
onEntry
\end{verbatim}
\section{Conditions}\label{conditions}
Conditions let you inspect the asset (or its execution context) and do
something, like send it to a particular transition.
Here's the \texttt{determine-branch} condition from the Category
Specific Approval workflow definition:
\begin{verbatim}
determine-branch
groovy
Legal Review
legal-review
false
Content Review
content-review
false
\end{verbatim}
This example checks the asset category to choose the processing path,
whether to transition to the \emph{Legal Review} task or the
\emph{Content Review} task.
The \texttt{returnValue} variable points from the condition to a
transition, and its value must match a valid transition name. This
script looks up the asset in question, retrieves its asset category, and
sets an initial \texttt{returnValue}. Then it checks to see if the asset
has been marked with the \emph{legal} category. If not it goes through
\emph{Content Review} (to the content-review task in the workflow), and
if it does it goes through \emph{Legal Review} (to the legal-review task
in the workflow).
\section{Forks and Joins}\label{forks-and-joins}
Forks split the workflow process, and joins bring the process back to a
unified branch. Processing must always be brought back using a Join (or
a Join XOR), and the number of forks and joins in a workflow definition
must be equal.
\begin{verbatim}
fork-1
transition-1
task-1
true
transition-2
task-2
false
join-1
transition-4
EndNode
true
\end{verbatim}
The workflow doesn't move past the join until the asset transitions to
it from both of the forks. To fork the workflow process, but then allow
the processing to continue when only one fork is completed, use a Join
XOR.
A Join XOR differs from a join in one important way: it removes the
constraint that both forks must be completed before processing can
continue. The asset must complete just one of the forks before
processing continues.
\begin{verbatim}
join-xor
transition3
EndNode
true
\end{verbatim}
\section{Task Nodes}\label{task-nodes}
\href{/docs/7-2/reference/-/knowledge_base/r/workflow-task-nodes}{Task
nodes} are at the core of the workflow definition. They're the part
where a user interacts with the asset in some way. Tasks can also have
sub-elements, including notifications, assignments, and task timers.
Here's the \texttt{content-review} task from the Category Specific
Approval workflow, with some of the \texttt{role} assignment tags cut
out for brevity:
\begin{verbatim}
content-review
Review Notification
You have a new submission waiting for your review in the workflow.
text
email
user-notification
onAssignment
organization
Organization Administrator
...
1
hour
false
text
user-notification
approve
approved
true
reject
update
false
\end{verbatim}
Learn more about workflow tasks in the next article.
\chapter{Workflow Task Nodes}\label{workflow-task-nodes}
Task nodes are fundamental parts of a workflow definition. When you
define your organization's business processes and design corresponding
workflows, you likely first envision the tasks. As the name implies,
tasks are the part of the workflow where \emph{work} is done. A user
enters the picture and must interact with the submitted asset. Users
often take the role of reviewer, deciding if an asset from the workflow
is acceptable for publication or needs more work.
Unlike other workflow nodes, task nodes have Assignments, because a user
is expected to \emph{do something} (often approve or reject the
submitted asset) when a workflow process enters the task node.
Commonly, task nodes contain task timers, assignments, actions (which
can include notifications and scripts), and transitions. Notifications
and actions aren't limited to task nodes, but task nodes and their
assignments deserve their own article (this one).
Check out the Review task in the Single Approver definition, noting that
several \texttt{\textless{}role\textgreater{}} tags are excluded from
this snippet for brevity:
\begin{verbatim}
review
Review Notification
${userName} sent you a ${entryType} for review in the workflow.
freemarker
email
user-notification
onAssignment
Review Completion Notification
and the reviewer applied the following ${taskComments}#if>.]]>
freemarker
email
onExit
organization
Organization Administrator
...
approve
approved
reject
update
false
\end{verbatim}
There are two \texttt{actions} in the review task, both
\texttt{\textless{}notification\textgreater{}}s. Each notification may
contain a name, template, notification-type, execution-type, and
recipients. Besides notifications, You can also use the
\texttt{\textless{}action\textgreater{}} tag. These have a name and a
\href{/docs/7-2/user/-/knowledge_base/u/leveraging-the-script-engine-in-workflow}{script}
and are more often used in state nodes than tasks.
\section{Assignments}\label{assignments}
Workflow tasks are completed by a user. Assignments make sure the right
users can access the tasks. You can choose how you want to configure
your assignments.
You can choose to add assignments to specific roles, to multiple roles
of a role type (organization, site, or regular role types), to the asset
creator, to resource actions, or to specific users. Additionally, you
can write a script to define the assignment. For an example, see the
\texttt{single-approver-definition-scripted-assignment.xml}.
\begin{verbatim}
organization
Organization Administrator
\end{verbatim}
The above assignment specifies that an Organization Administrator must
complete the task.
\begin{verbatim}
20156
\end{verbatim}
The above assignment specifies that only the user with the user ID of
20156 may complete the task. Alternatively, specify the
\texttt{\textless{}screen-name\textgreater{}} or
\texttt{\textless{}email-address\textgreater{}} of the user.
\begin{verbatim}
groovy
\end{verbatim}
The above assignment assigns the task to the \emph{Administrator} role,
then checks whether the \emph{group} of the asset is an Organization. If
it is, the \emph{Organization Content Reviewer} role is assigned to it.
If it's not, the task is assigned to the \emph{Site Content Reviewer}
role.
Note the
\texttt{roles\ =\ new\ ArrayList\textless{}Role\textgreater{}();} line
above. In a scripted assignment, the \texttt{roles} variable is where
you specify any roles the task is assigned to. For example, when
\texttt{roles.add(adminRole);} is called, the Administrator role is
added to the assignment.
Assigning tasks to Roles, Organizations, or Asset Creators is a
straightforward concept, but what does it mean to assign a workflow task
to a Resource Action? Imagine an \emph{UPDATE} resource action. If your
workflow definition specifies the UPDATE action in an assignment, then
anyone who has permission to update the type of asset being processed in
the workflow is assigned to the task. You can configure multiple
assignments for a task.
\section{Resource Action Assignments}\label{resource-action-assignments}
\emph{Resource actions} are operations performed by users on an
application or entity. For example, a user might have permission to
update Message Boards Messages. This is called an UPDATE resource
action, because the user can update the resource. If you're uncertain
about what resource actions are, refer to the developer tutorial on the
\href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{permission
system} for a more detailed explanation.
To find all the resource actions that have been created, you need access
to the Roles Admin application in the Control Panel (in other words, you
need permission for the VIEW action on the roles resource).
\begin{itemize}
\tightlist
\item
Navigate to Control Panel → Users → Roles.
\item
Add a new Regular Role. See the
\href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{article
on managing roles} for more information.
\item
Once the role is added, navigate to the Define Permissions interface
for the role.
\item
Find the resource whose action should define your workflow assignment.
\end{itemize}
Here's what the assignment's XML looks like:
\begin{verbatim}
UPDATE
\end{verbatim}
Now when the workflow proceeds to the task with the resource action
assignment, users with \texttt{UPDATE} permission on the resource (for
example, Message Boards Messages) are notified of the task and can
assign it to themselves (if the notification is set to Task Assignees).
Specifically, users see the tasks in their \emph{My Workflow Tasks}
application under the tab \emph{Assigned to My Roles}.
Use all upper case letters for resource action names. Here are some
common resource actions:
\begin{verbatim}
UPDATE
ADD
DELETE
VIEW
PERMISSIONS
SUBSCRIBE
ADD_DISCUSSION
\end{verbatim}
Determine the probable resource action name from the permissions screen
for a resource. For example, in Message Boards, one of the permissions
displayed on that screen is \emph{Add Discussion}. Convert that to all
uppercase and replace the space with an underscore, and you have the
action name.
\section{Task Timers}\label{task-timers}
Task timers trigger an action after a specified time period passes.
Timers are useful for ensuring a task does not go unattended for a long
time. Available timer actions include sending an additional
notification, reassigning the asset, or creating a timer action.
\begin{verbatim}
1
hour
false
10
minute
text
user-notification
\end{verbatim}
The above task timer creates a notification. Specify a time period in
the \texttt{\textless{}delay\textgreater{}} tag, and specify what action
to take when the time expires in the
\texttt{\textless{}timer-actions\textgreater{}} block. The
\texttt{\textless{}blocking\textgreater{}} element specifies whether the
timer actions may recur. If blocking is set to \texttt{false}, timer
actions may recur. In a \texttt{recurrence} element, specify the
recurrence interval using a \texttt{duration} and a \texttt{scale}, as
demonstrated above. The above recurrence element specifies that the
timer actions run again every ten minutes after the initial occurrence.
Setting blocking to true prevents timer actions from recurring.
\begin{verbatim}
...
\end{verbatim}
The above snippet demonstrates how to set up a reassignment action.
Like \texttt{\textless{}action\textgreater{}} elements,
\texttt{\textless{}timer-action\textgreater{}} elements can contain
scripts.
\begin{verbatim}
doSomething
Do something cool when time runs out.
groovy
\end{verbatim}
The above example isn't functional but it demonstrates setting up a
\texttt{\textless{}script\textgreater{}} in your task timer.
\href{/docs/7-2/user/-/knowledge_base/u/leveraging-the-script-engine-in-workflow}{Read
the \emph{Scripting in Workflow} article} for more information.
\noindent\hrulefill
\textbf{Note:} A \texttt{timer-action} can contain all the same tags as
an \texttt{action}, with one exception: \texttt{execution-type}. Timer
actions are always triggered once the time is up, so specifying and
execution type of \texttt{onEntry}, for example, isn't meaningful inside
a timer.
\noindent\hrulefill
Tasks are at the core of the workflow definition. Once you understand
how to create tasks and the other
\href{/docs/7-2/reference/-/knowledge_base/r/workflow-definition-nodes}{workflow
nodes} and add transitions between the nodes, you're on the cusp of
workflow wizard-hood.
\chapter{Workflow Notifications}\label{workflow-notifications}
While an asset is in a workflow, relevant Users should be notified about
certain events, like when a review task is completed. Any workflow node
with an \texttt{\textless{}actions\textgreater{}} element can have
notifications.
\begin{verbatim}
Creator Modification Notification
Your submission was rejected by ${userName}, please modify and resubmit.
freemarker
email
user-notification
onAssignment
\end{verbatim}
The above Creator Modification Notification sends a notification message
in two ways: via email and via user notification (this goes to the
Notifications widget in the User's Site). The message is defined in a
FreeMarker template and sent once a task assignment is created. But who
receives the notification? If no recipients are explicitly specified via
a \texttt{recipients} tag, the asset's creator receives the
notification.
\section{Notification Options}\label{notification-options}
There are several elements that can be specified in a
\texttt{\textless{}notification\textgreater{}}:
\begin{description}
\tightlist
\item[\textbf{Name}]
Set the name of the notification in the
\texttt{\textless{}name\textgreater{}} element. This information is used
to display the notification in the \emph{My Workflow Tasks} widget of a
User's personal Site.
\item[\textbf{Description}]
The \texttt{\textless{}description\textgreater{}} element contains the
subject of the notification if the notification type is \texttt{email}.
The syntax is determined by the template language you're using.
\item[\textbf{Template}]
The \texttt{\textless{}template\textgreater{}} element contains the
message of the notification. The syntax is determined by the template
language you're using.
\item[\textbf{Template Language}]
Choose from \texttt{freemarker}, \texttt{velocity}, or plain
\texttt{text} in the \texttt{\textless{}template-language\textgreater{}}
tag.
\item[\textbf{Notification Type}]
Choose whether to send an \texttt{email}, \texttt{user-notification}
(via the Notification widget), \texttt{im} (instant message), or
\texttt{private-message} in the
\texttt{\textless{}notification-type\textgreater{}} tag.
\end{description}
\begin{verbatim}
email
\end{verbatim}
\begin{description}
\tightlist
\item[\textbf{Execution Type}]
Choose to link the sending of the notification to entry into the node
(\texttt{onEntry}), when a task is assigned (\texttt{onAssignment}), or
when the workflow processing is leaving a node (\texttt{onExit}). If you
specify a notification to be sent on assignment, the assignee is
notified automatically.
\item[\textbf{Recipients}]
Decide who should receive the notification in the
\texttt{\textless{}recipients\textgreater{}} tag:
\end{description}
\begin{verbatim}
[SEE BELOW FOR THE AVAILABLE RECIPIENT TAGS]
\end{verbatim}
Available recipient tags are
\begin{itemize}
\tightlist
\item
\texttt{\textless{}user\textgreater{}}: notify the User that sent the
asset through the workflow. Specify the tag as
\texttt{\textless{}user\ /\textgreater{}}. To notify a specific user,
enter the \texttt{userId}:
\end{itemize}
\begin{verbatim}
20139
\end{verbatim}
\begin{itemize}
\tightlist
\item
\texttt{\textless{}roles\textgreater{}}: notify specific Roles, either
by ID or by their type and name.
\end{itemize}
\begin{verbatim}
33621
regular
Power User
false
\end{verbatim}
\begin{itemize}
\item
\texttt{\textless{}assignees\ /\textgreater{}}: notify the task
assignees.
\item
\texttt{\textless{}scripted-recipient\textgreater{}}: use a script to
identify notification recipients.
\end{itemize}
\begin{verbatim}
groovy
\end{verbatim}
If the notification type is \texttt{email}, you can specify the
\texttt{recipientType} attribute of the
\texttt{\textless{}recipients\textgreater{}} tag as \emph{To},
\emph{CC}, or \emph{BCC}.
\begin{verbatim}
regular
Manager
\end{verbatim}
By default, \texttt{recipientType} is \texttt{to}.
As always, read the
\href{https://www.liferay.com/dtd/liferay-workflow-definition_7_1_0.xsd}{schema
for all the details}.
\chapter{Breaking Changes}\label{breaking-changes}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
This document presents a chronological list of changes that break
existing functionality, APIs, or contracts with third party Liferay
developers or users. We try our best to minimize these disruptions, but
sometimes they are unavoidable.
Here are some of the types of changes documented in this file:
\begin{itemize}
\tightlist
\item
Functionality that is removed or replaced
\item
API incompatibilities: Changes to public Java or JavaScript APIs
\item
Changes to context variables available to templates
\item
Changes in CSS classes available to Liferay themes and portlets
\item
Configuration changes: Changes in configuration files, like
\texttt{portal.properties}, \texttt{system.properties}, etc.
\item
Execution requirements: Java version, J2EE Version, browser versions,
etc.
\item
Deprecations or end of support: For example, warning that a certain
feature or API will be dropped in an upcoming version.
\item
Recommendations: For example, recommending using a newly introduced
API that replaces an old API, in spite of the old API being kept in
Liferay Portal for backwards compatibility.
\end{itemize}
\section{Breaking Changes List}\label{breaking-changes-list}
\section{Removed Support for JSP Templates in
Themes}\label{removed-support-for-jsp-templates-in-themes}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2018-Nov-14
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-87064}{LPS-87064}
\end{itemize}
\subsection{What changed?}\label{what-changed}
Themes can no longer leverage JSP templates. Also, related logic has
been removed from the public APIs
\texttt{com.liferay.portal.kernel.util.ThemeHelper} and
\texttt{com.liferay.taglib.util.ThemeUtil}.
\subsection{Who is affected?}\label{who-is-affected}
This affects anyone who has themes using JSP templates or is using the
removed methods.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code}
If you have a theme using JSP templates, consider migrating it to
FreeMarker.
\subsection{Why was this change made?}\label{why-was-this-change-made}
JSP is not a real template engine and is rarely used. FreeMarker is the
recommended template engine moving forward.
The removal of JSP templates allows for an increased focus on existing
and new template engines.
\section{Lodash Is No Longer Included by
Default}\label{lodash-is-no-longer-included-by-default}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2018-Nov-27
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-87677}{LPS-87677}
\end{itemize}
\subsection{What changed?}\label{what-changed-1}
Previously, Lodash was included in every page by default and made
available through the global \texttt{window.\_} and scoped
\texttt{AUI.\_} variables. Lodash is no longer included by default and
those variables are now undefined.
\subsection{Who is affected?}\label{who-is-affected-1}
This affects any developer who used the \texttt{AUI.\_} or
\texttt{window.\_} variables in their custom scripts.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-1}
You should provide your own Lodash version for your custom developments
to use following any of the possible strategies to add third party
libraries.
As a temporary measure, you can bring back the old behavior by setting
the \emph{Enable Lodash} property in Liferay Portal's \emph{Control
Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Third
Party} → \emph{Lodash} to \texttt{true}.
\subsection{Why was this change made?}\label{why-was-this-change-made-1}
This change was made to avoid bundling and serving additional library
code on every page that was mostly unused and redundant.
\section{Moved Two Staging Properties to OSGi
Configuration}\label{moved-two-staging-properties-to-osgi-configuration}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2018-Dec-12
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88018}{LPS-88018}
\end{itemize}
\subsection{What changed?}\label{what-changed-2}
Two Staging properties have been moved from \texttt{portal.properties}
to an OSGi configuration named
\texttt{ExportImportServiceConfiguration.java} in the
\texttt{export-import-service} module.
\subsection{Who is affected?}\label{who-is-affected-2}
This affects anyone using the following portal properties:
\begin{itemize}
\tightlist
\item
\texttt{staging.delete.temp.lar.on.failure}
\item
\texttt{staging.delete.temp.lar.on.success}
\end{itemize}
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-2}
Instead of overriding the \texttt{portal.properties} file, you can
manage the properties from Portal's configuration administrator. This
can be accessed by navigating to Liferay Portal's \emph{Control Panel} →
\emph{Configuration} → \emph{System Settings} → \emph{Infrastructure} →
\emph{Export/Import} and editing the settings there.
If you would like to include the new configuration in your application,
follow the instructions for
\href{https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-1/making-applications-configurable}{making
applications configurable}.
\subsection{Why was this change made?}\label{why-was-this-change-made-2}
This change was made as part of the modularization efforts to ease
portal configuration changes.
\section{Remove Link Application URLs to Page
Functionality}\label{remove-link-application-urls-to-page-functionality}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2018-Dec-14
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-85948}{LPS-85948}
\end{itemize}
\subsection{What changed?}\label{what-changed-3}
The \emph{Link Portlet URLs to Page} option in the Look and Feel portlet
was marked as deprecated in Liferay Portal 7.1, allowing the user to
show and hide the option through a configuration property. In Liferay
Portal 7.2, this has been removed and can no longer be configured.
\subsection{Who is affected?}\label{who-is-affected-3}
This affects administrators who used the option in the UI and developers
who leveraged the option in the portlet.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-3}
You should update any portlets leveraging this feature, since any
preconfigured reference to the property is ignored in the portal.
\subsection{Why was this change made?}\label{why-was-this-change-made-3}
A limited number of portlets use this property; there are better ways to
achieve the same results.
\section{Moved TermsOfUseContentProvider out of
kernel.util}\label{moved-termsofusecontentprovider-out-of-kernel.util}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-07
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88869}{LPS-88869}
\end{itemize}
\subsection{What changed?}\label{what-changed-4}
The \texttt{TermsOfUseContentProvider} interface's package changed:
\texttt{com.liferay.portal.kernel.util} →
\texttt{com.liferay.portal.kernel.term.of.use}
The \texttt{TermsOfUseContentProviderRegistryUtil} class' name and
package changed:
\texttt{TermsOfUseContentProviderRegistryUtil} →
\texttt{TermsOfUseContentProviderUtil}
and \texttt{com.liferay.portal.kernel.util} →
\texttt{com.liferay.portal.internal.terms.of.use}
The logic of getting \texttt{TermsOfUseContentProvider} was also
changed. Instead of always returning the first service registered, which
is random and depends on the order of registered services, the
\texttt{TermsOfUseContentProvider} service is tracked and updated with
\texttt{com.liferay.portal.kernel.util.ServiceProxyFactory}. As a
result, the \texttt{TermsOfUseContentProvider} now respects service
ranking.
\subsection{Who is affected?}\label{who-is-affected-4}
This affects anyone who used
\texttt{com.liferay.portal.kernel.util.TermsOfUseContentProviderRegistryUtil}
to lookup the
\texttt{com.liferay.portal.kernel.util.TermsOfUseContentProvider}
service.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-4}
If \texttt{com.liferay.portal.kernel.util.TermsOfUseContentProvider} is
used, update the import package name. If there is any usage in
\texttt{portal-web}, update
\texttt{com.liferay.portal.kernel.util.TermsOfUseContentProviderRegistryUtil}
to
\texttt{com.liferay.portal.kernel.term.of.use.TermsOfUseContentProviderUtil}.
Remove usages of
\texttt{com.liferay.portal.kernel.util.TermsOfUseContentProviderRegistryUtil}
in modules and use the \texttt{@Reference} annotation to fetch the
\texttt{com.liferay.portal.kernel.term.of.use.TermsOfUseContentProvider}
service instead.
\subsection{Why was this change made?}\label{why-was-this-change-made-4}
This is one of several steps to clean up kernel provider interfaces to
reduce the chance of package version lock down.
\section{Removed HibernateConfigurationConverter and
Converter}\label{removed-hibernateconfigurationconverter-and-converter}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-07
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88870}{LPS-88870}
\end{itemize}
\subsection{What changed?}\label{what-changed-5}
The interface \texttt{com.liferay.portal.kernel.util.Converter} and its
implementation
\texttt{com.liferay.portal.spring.hibernate.HibernateConfigurationConverter}
were removed.
\subsection{Who is affected?}\label{who-is-affected-5}
This removes the support of generating customized
\texttt{portlet-hbm.xml} files implemented by
\texttt{HibernateConfigurationConverter}. Refer to
\href{https://issues.liferay.com/browse/LPS-5363}{LPS-5363} for more
information.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-5}
You should remove usages of \texttt{HibernateConfigurationConverter}.
Make sure the generated \texttt{portlet-hbm.xml} is accurate.
\subsection{Why was this change made?}\label{why-was-this-change-made-5}
This is one of several steps to clean up kernel provider interfaces to
reduce the chance of package version lock down.
\section{Switched to Use JDK Function and
Supplier}\label{switched-to-use-jdk-function-and-supplier}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-08
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88911}{LPS-88911}
\end{itemize}
\subsection{What changed?}\label{what-changed-6}
The \texttt{Function} and \texttt{Supplier} interfaces in package
\texttt{com.liferay.portal.kernel.util} were removed. Their usages were
replaced with \texttt{java.util.function.Function} and
\texttt{java.util.function.Supplier}.
\subsection{Who is affected?}\label{who-is-affected-6}
This affects anyone who implemented the \texttt{Function} and
\texttt{Supplier} interfaces in package
\texttt{com.liferay.portal.kernel.util}.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-6}
You should replace usages of
\texttt{com.liferay.portal.kernel.util.Function} and
\texttt{com.liferay.portal.kernel.util.Supplier} with
\texttt{java.util.function.Function} and
\texttt{java.util.function.Supplier}, respectively.
\subsection{Why was this change made?}\label{why-was-this-change-made-6}
This is one of several steps to clean up kernel provider interfaces to
reduce the chance of package version lock down.
\section{Deprecated com.liferay.portal.service.InvokableService
Interface}\label{deprecated-com.liferay.portal.service.invokableservice-interface}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-08
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88912}{LPS-88912}
\end{itemize}
\subsection{What changed?}\label{what-changed-7}
The \texttt{InvokableService} and \texttt{InvokableLocalService}
interfaces in package \texttt{com.liferay.portal.kernel.service} were
removed.
\subsection{Who is affected?}\label{who-is-affected-7}
This affects anyone who used \texttt{InvokableService} and
\texttt{InvokableLocalService} in package
\texttt{com.liferay.portal.kernel.service}.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-7}
You should remove usages of \texttt{InvokableService} and
\texttt{InvokableLocalService}. Make sure to use the latest version of
Service Builder to generate implementations for services in case there
is any compile errors after removal.
\subsection{Why was this change made?}\label{why-was-this-change-made-7}
This is one of several steps to clean up kernel provider interfaces to
reduce the chance of package version lock down.
\section{Dropped Support of
ServiceLoaderCondition}\label{dropped-support-of-serviceloadercondition}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-08
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88913}{LPS-88913}
\end{itemize}
\subsection{What changed?}\label{what-changed-8}
The interface \texttt{ServiceLoaderCondition} and its implementation
\texttt{DefaultServiceLoaderCondition} in package
\texttt{com.liferay.portal.kernel.util} were removed.
\subsection{Who is affected?}\label{who-is-affected-8}
This affects anyone using \texttt{ServiceLoaderCondition} and
\texttt{DefaultServiceLoaderCondition}.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-8}
You should remove usages of \texttt{ServiceLoaderCondition}. Update
usages of \texttt{load} methods in
\texttt{com.liferay.portal.kernel.util.ServiceLoader} according to the
updated method signatures.
\subsection{Why was this change made?}\label{why-was-this-change-made-8}
This is one of several steps to clean up kernel provider interfaces to
reduce the chance of package version lock down.
\section{Switched to Use JDK
Predicate}\label{switched-to-use-jdk-predicate}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-14
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-89139}{LPS-89139}
\end{itemize}
\subsection{What changed?}\label{what-changed-9}
The interface \texttt{com.liferay.portal.kernel.util.PredicateFilter}
was removed and replaced with \texttt{java.util.function.Predicate}. As
a result, the following implementations were removed:
\begin{itemize}
\tightlist
\item
\texttt{com.liferay.portal.kernel.util.AggregatePredicateFilter}
\item
\texttt{com.liferay.portal.kernel.util.PrefixPredicateFilter}
\item
\texttt{com.liferay.portal.kernel.portlet.JavaScriptPortletResourcePredicateFilter}
\item
\texttt{com.liferay.dynamic.data.mapping.form.values.query.internal.model.DDMFormFieldValuePredicateFilter}
\end{itemize}
The \texttt{com.liferay.portal.kernel.util.ArrayUtil\_IW} class was
regenerated.
\subsection{Who is affected?}\label{who-is-affected-9}
This affects anyone who used \texttt{PredicateFilter},
\texttt{AggregatePredicateFilter}, \texttt{PrefixPredicateFilter},
\texttt{JavaScriptPortletResourcePredicateFilter}, and
\texttt{DDMFormFieldValuePredicateFilter}.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-9}
You should replace usages of
\texttt{com.liferay.portal.kernel.util.PredicateFilter} with
\texttt{java.util.function.Predicate}. Additionally, remove usages of
\texttt{AggregatePredicateFilter}, \texttt{PrefixPredicateFilter},
\texttt{JavaScriptPortletResourcePredicateFilter}, and
\texttt{DDMFormFieldValuePredicateFilter}.
\subsection{Why was this change made?}\label{why-was-this-change-made-9}
This is one of several steps to clean up kernel provider interfaces to
reduce the chance of package version lock down.
\section{Removed Unsafe Functional Interfaces in Package
com.liferay.portal.kernel.util}\label{removed-unsafe-functional-interfaces-in-package-com.liferay.portal.kernel.util}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-15
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-89223}{LPS-89223}
\end{itemize}
\subsection{What changed?}\label{what-changed-10}
The \texttt{com.liferay.portal.osgi.util.test.OSGiServiceUtil} class was
removed. Also, the following interfaces were removed from the
\texttt{com.liferay.portal.kernel.util} package:
\begin{itemize}
\tightlist
\item
\texttt{UnsafeConsumer}
\item
\texttt{UnsafeFunction}
\item
\texttt{UnsafeRunnable}
\end{itemize}
\subsection{Who is affected?}\label{who-is-affected-10}
This affects anyone using the class/interfaces mentioned above.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-10}
The \texttt{com.liferay.portal.osgi.util.test.OSGiServiceUtil} class has
been deprecated since Liferay Portal 7.1. If usages for this class still
exist, replace it with its direct replacement:
\texttt{com.liferay.osgi.util.service.OSGiServiceUtil}. Replace usages
of \texttt{UnsafeConsumer}, \texttt{UnsafeFunction} and
\texttt{UnsafeRunnable} with their corresponding interfaces in package
\texttt{com.liferay.petra.function}.
\subsection{Why was this change
made?}\label{why-was-this-change-made-10}
This is one of several steps to clean up kernel provider interfaces to
reduce the chance of package version lock down.
\section{Deprecated NTLM in Portal
Distribution}\label{deprecated-ntlm-in-portal-distribution}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-21
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88300}{LPS-88300}
\end{itemize}
\subsection{What changed?}\label{what-changed-11}
NTLM modules have been moved from the \texttt{portal-security-sso}
project to a new project named \texttt{portal-security-sso-ntlm}. This
new project is deprecated and available to download from Liferay
Marketplace.
\subsection{Who is affected?}\label{who-is-affected-11}
This affects anyone using NTLM as an authentication system.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-11}
If you want to continue using NTLM as an authentication system, you must
download the corresponding modules from Liferay Marketplace.
Alternatively, you can migrate to Kerberos (recommended), which requires
no changes and is compatible with Liferay Portal 7.0+.
\subsection{Why was this change
made?}\label{why-was-this-change-made-11}
This change was made to avoid using an old proprietary solution (NTLM).
Kerberos is now recommended, which is a standard protocol and a more
secure method of authentication compared to NTLM.
\section{Deprecated OpenID in Portal
Distribution}\label{deprecated-openid-in-portal-distribution}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-21
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88906}{LPS-88906}
\end{itemize}
\subsection{What changed?}\label{what-changed-12}
OpenID modules have been moved to a new project named
\texttt{portal-security-sso-openid}. This new project is deprecated and
available to download from Liferay Marketplace.
\subsection{Who is affected?}\label{who-is-affected-12}
This affects anyone using OpenID as an authentication system.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-12}
If you want to continue using OpenID as an authentication system, you
must download the corresponding module from Liferay Marketplace.
Alternatively, you should migrate to OpenID Connect, available on
Liferay Portal Distribution.
\subsection{Why was this change
made?}\label{why-was-this-change-made-12}
This change was made to avoid using a deprecated solution (OpenID).
OpenID Connect is now recommended, which is a more secure method of
authentication since it runs on top of OAuth.
\section{Deprecated Google SSO in Portal
Distribution}\label{deprecated-google-sso-in-portal-distribution}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Jan-21
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-88905}{LPS-88905}
\end{itemize}
\subsection{What changed?}\label{what-changed-13}
Google SSO modules have been moved from the \texttt{portal-security-sso}
project to a new project named \texttt{portal-security-sso-google}. This
new project is deprecated and available to download from Liferay
Marketplace.
\subsection{Who is affected?}\label{who-is-affected-13}
This affects anyone using Google SSO as an authentication system.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-13}
If you want to continue using Google SSO as an authentication system,
you must download the corresponding module from Liferay Marketplace.
Alternatively, you can use OpenID Connect.
\subsection{Why was this change
made?}\label{why-was-this-change-made-13}
This change was made to avoid using an old solution for authentication
(Google SSO). OpenID Connect is the recommended specification to use
Google implementation for authentication.
\section{Updated AlloyEditor v2.0 Includes New Major Version of
React}\label{updated-alloyeditor-v2.0-includes-new-major-version-of-react}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Feb-04
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-90079}{LPS-90079}
\end{itemize}
\subsection{What changed?}\label{what-changed-14}
AlloyEditor was upgraded to version 2.0.0, which includes a major
upgrade from React v15 to v16.
The \texttt{React.createClass} was
\href{https://reactjs.org/blog/2017/04/07/react-v15.5.0.html}{deprecated
in React v15.5.0} (April 2017) and
\href{https://reactjs.org/blog/2017/09/26/react-v16.0.html}{removed in
React v16.0.0} (September 2017). All the buttons bundled with
AlloyEditor have been updated to use the ES6 class syntax instead of
\texttt{React.createClass}.
\subsection{Who is affected?}\label{who-is-affected-14}
This affects anyone who built their own buttons using
\texttt{React.createClass}. The \texttt{createClass} function is no
longer available, and attempts to access it at runtime will trigger an
error.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-14}
You should update your code in one of two ways:
\begin{itemize}
\item
Port custom buttons from the \texttt{React.createClass} API to use the
ES6 \texttt{class} API, as described in
\href{https://reactjs.org/docs/react-component.html}{the React
documentation}. For example, see the changes made in moving to an
\href{https://github.com/liferay/alloy-editor/blob/b082c312179ae6626cb2ddcc04ad3ebc5b355e1b/src/components/buttons/button-ol.jsx}{ES6
class-based button} from
\href{https://github.com/liferay/alloy-editor/blob/2826ab9ceabe17c6ba0d38985baf8a787c23db43/src/ui/react/src/components/buttons/button-ol.jsx}{the
previous \texttt{createClass}-based implementation}.
\item
Provide a compatibility adapter. The
\href{https://www.npmjs.com/package/create-react-class}{create-react-class
package} (described
\href{https://reactjs.org/docs/react-without-es6.html}{here}) can be
injected into the page to restore the \texttt{createClass} API.
\end{itemize}
\subsection{Why was this change
made?}\label{why-was-this-change-made-14}
This change was made to use a newer major version of React, which brings
performance and compatibility improvements and reduces the bundle size
by removing deprecated APIs.
\section{Deprecated dl.tabs.visible
property}\label{deprecated-dl.tabs.visible-property}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Apr-10
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-93948}{LPS-93948}
\end{itemize}
\subsection{What changed?}\label{what-changed-15}
The \texttt{dl.tabs.visible} property let users toggle the visibility of
a Documents and Media widget's navigation tabs when placed on a widget
page. This configuration option has been removed, so the navigation tab
will never appear on widget pages.
\subsection{Who is affected?}\label{who-is-affected-15}
This affects anyone who set the \texttt{dl.tabs.visible} property to
\texttt{true}.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-15}
No code changes are necessary.
\subsection{Why was this change
made?}\label{why-was-this-change-made-15}
Documents \& Media has been reviewed from a UX perspective, and removing
the navigation tabs in widget pages was part of a UI clean up process.
\section{Move the User Menu out of the Product
Menu}\label{move-the-user-menu-out-of-the-product-menu}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Apr-19
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-87868}{LPS-87868}
\end{itemize}
\subsection{What changed?}\label{what-changed-16}
The User Menu was removed from the Product Menu, and the user menu
entries were moved to the new Personal Menu, a dropdown menu triggered
by the user avatar.
\subsection{Who is affected?}\label{who-is-affected-16}
This affects anyone who has customized the User Menu section of the
Product Menu.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-16}
If you would like to keep your custom user menu entries and have them
available in the Personal Menu, you need to implement the
\texttt{PersonalMenuEntry} interface. All panel apps registered with the
\texttt{PanelCategoryKeys.USER},
\texttt{PanelCategoryKeys.USER\_MY\_ACCOUNT}, and
\texttt{PanelCategoryKeys.USER\_SIGN\_OUT} panel category keys should be
converted to \texttt{PersonalMenuEntry}.
\subsection{Why was this change
made?}\label{why-was-this-change-made-16}
Product navigation has been reviewed from a UX perspective, and removing
the User Menu from the Product Menu and splitting the menu to its own
provides a better user experience.
\section{Removed Hong Kong and Macau from the List of
Countries}\label{removed-hong-kong-and-macau-from-the-list-of-countries}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Apr-26
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-82203}{LPS-82203}
\end{itemize}
\subsection{What changed?}\label{what-changed-17}
Hong Kong and Macau have been removed from the list of countries and
listed as regions of China as Xianggang (region code: CN-91) and Aomen
(region code: CN-92), respectively.
\subsection{Who is affected?}\label{who-is-affected-17}
This affects anyone who used Hong Kong or Macau in their addresses.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-17}
No code changes are necessary. However, if you have hardcoded the
\texttt{countryId} of Hong Kong and Macau in your code, they should be
updated to China's \texttt{countryId}. References to Hong Kong and Macau
should be done with their corresponding \texttt{regionId}.
\subsection{Why was this change
made?}\label{why-was-this-change-made-17}
After the handover of Hong Kong in 1997 and of Macau in 1999, Hong Kong
and Macau are now the special administrative regions of China.
\section{JGroups Was Upgraded From 3.6.16 to
4.1.1}\label{jgroups-was-upgraded-from-3.6.16-to-4.1.1}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Aug-15
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-97897}{LPS-97897}
\end{itemize}
\subsection{What changed?}\label{what-changed-18}
JGroups was upgraded from version 3.6.16 to 4.1.1.
\subsection{Who is affected?}\label{who-is-affected-18}
This affects anyone using Cluster Link.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-18}
The \texttt{cluster.link.channel.properties.*} property in
\texttt{portal.properties} no longer accepts a connection string as a
value; it now requires a file path to a configuration XML file. Some of
the protocol properties from 3.6.16 are removed and no longer parsed by
4.1.1; you should update the protocol properties accordingly.
\subsection{Why was this change
made?}\label{why-was-this-change-made-18}
This upgrade was made to fix a security issue.
\section{\texorpdfstring{Liferay \texttt{AssetEntries\_AssetCategories}
Is No Longer
Used}{Liferay AssetEntries\_AssetCategories Is No Longer Used}}\label{liferay-assetentries_assetcategories-is-no-longer-used}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2019-Sep-11
\item
\textbf{JIRA Tickets:}
\href{https://issues.liferay.com/browse/LPS-99973}{LPS-99973},
\href{https://issues.liferay.com/browse/LPS-76488}{LPS-76488}
\end{itemize}
\subsection{What changed?}\label{what-changed-19}
Previously, Liferay used a mapping table and a corresponding interface
for the relationship between \texttt{AssetEntry} and
\texttt{AssetCategory} in \texttt{AssetEntryLocalService} and
\texttt{AssetCategoryLocalService}. This mapping table and the
corresponding interface have been replaced by the table
\texttt{AssetEntryAssetCategoryRel} and the service
\texttt{AssetEntryAssetCategoryRelLocalService}.
\subsection{Who is affected?}\label{who-is-affected-19}
This affects any content or code that relies on calling the old
interfaces for the \texttt{AssetEntries\_AssetCategories} relationship,
through the \texttt{AssetEntryLocalService} and
\texttt{AssetCategoryLocalService}.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-19}
Use the new methods in \texttt{AssetEntryAssetCategoryRelLocalService}
to retrieve the same data as before. The method signatures haven't
changed; they have just been relocated to a different service.
\textbf{Example}
Old way:
\begin{verbatim}
List entries =
AssetEntryLocalServiceUtil.getAssetCategoryAssetEntries(categoryId);
for (AssetEntry entry: entries) {
...
}
\end{verbatim}
New way:
\begin{verbatim}
long[] assetEntryPKs =
_assetEntryAssetCategoryRelLocalService.getAssetEntryPrimaryKeys(assetCategoryId);
for (long assetEntryPK: assetEntryPKs) {
AssetEntry = _assetEntryLocalService.getEntry(assetEntryPK);
...
}
...
@Reference
private AssetEntryAssetCategoryRelLocalService _assetEntryAssetCategoryRelLocalService;
@Reference
private AssetEntryLocalService _assetEntryLocalService;
\end{verbatim}
\subsection{Why was this change
made?}\label{why-was-this-change-made-19}
This change was made due to changes resulting from
\href{https://issues.liferay.com/browse/LPS-76488}{LPS-76488}, which let
developers control the order of a list of assets for a given category.
\section{Auto Tagging Must Be Reconfigured
Manually}\label{auto-tagging-must-be-reconfigured-manually}
\begin{itemize}
\tightlist
\item
\textbf{Date: 2019-Oct-2}
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-97123}{LPS-97123}
\end{itemize}
\subsection{What changed?}\label{what-changed-20}
Auto Tagging configurations were renamed and reorganized. There's no
longer an automatic upgrade process, so you must reconfigure Auto
Tagging manually.
\subsection{Who is affected?}\label{who-is-affected-20}
This affects DXP 7.2 installations that are upgraded to SP1 and have
Auto Tagging configured and enabled.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-20}
You must reconfigure Auto Tagging through System Settings (please see
the
\href{https://help.liferay.com/hc/en-us/articles/360029041551-Configuring-Asset-Auto-Tagging}{official
documentation} for details). Any code referencing the old configuration
interfaces must be updated to use the new ones.
\subsection{Why was this change
made?}\label{why-was-this-change-made-20}
This change unifies the previously split configuration interfaces,
improving the user experience.
\section{Blogs Image Properties Were Moved to System
Settings}\label{blogs-image-properties-were-moved-to-system-settings}
\begin{itemize}
\tightlist
\item
\textbf{Date: 2019-Oct-2}
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-95298}{LPS-95298}
\end{itemize}
\subsection{What changed?}\label{what-changed-21}
Blogs image configuration was moved from \texttt{portal.properties} to
System Settings. There's no automatic upgrade process, so custom Blogs
image properties must be reconfigured manually.
\subsection{Who is affected?}\label{who-is-affected-21}
This affects DXP 7.2 installations that are upgraded to SP1 and have
custom values for the \texttt{blogs.image.max.size} and
\texttt{blogs.image.extensions} properties.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-21}
If you would like to keep your custom Blogs image property values, you
must reconfigure them through the System Settings under
\emph{Configuration} → \emph{Blogs} → \emph{File Uploads}. Any code
referencing the old properties must be updated to use the new
configuration interfaces.
\subsection{Why was this change
made?}\label{why-was-this-change-made-21}
This change was made so Blogs image properties can be configured without
a restart.
\section{Removed Cache Bootstrap
Feature}\label{removed-cache-bootstrap-feature}
\begin{itemize}
\tightlist
\item
\textbf{Date:} 2020-Jan-8
\item
\textbf{JIRA Ticket:}
\href{https://issues.liferay.com/browse/LPS-96563}{LPS-96563}
\end{itemize}
\subsection{What changed?}\label{what-changed-22}
The cache bootstrap feature has been removed. These properties can no
longer be used to enable/configure cache bootstrap:
\texttt{ehcache.bootstrap.cache.loader.enabled},
\texttt{ehcache.bootstrap.cache.loader.properties.default},
\texttt{ehcache.bootstrap.cache.loader.properties.\$\{specific.cache.name\}}.
\subsection{Who is affected?}\label{who-is-affected-22}
This affects anyone using the properties listed above.
\subsection{How should I update my
code?}\label{how-should-i-update-my-code-22}
There's no direct replacement for the removed feature. If you have code
that depends on it, you must implement it yourself.
\subsection{Why was this change
made?}\label{why-was-this-change-made-22}
This change was made to avoid security issues.
\chapter{CDI Portlet Predefined
Beans}\label{cdi-portlet-predefined-beans}
Liferay DXP provides injectable portlet artifacts for
\href{/docs/7-2/frameworks/-/knowledge_base/f/cdi-dependency-injection}{CDI}
called Portlet Predefined Beans, as specified by
\href{https://jcp.org/en/jsr/detail?id=362}{JSR 362}. There are two
types of predefined beans:
\begin{itemize}
\item
Portlet Request Scoped Beans
(\href{https://docs.liferay.com/portlet\%20\%7C\%20-\%20\%7C\%20api/3.0/javadocs/javax/portlet/annotations/PortletRequestScoped.html}{\texttt{@PortletRequestScoped}})
\item
Dependent Scoped Beans
(\href{https://docs.oracle.com/javaee/7/api/javax/enterprise/context/Dependent.html}{\texttt{@Dependent}
scoped})
\end{itemize}
The table below describes these attributes for each bean:
\textbf{Artifact:} The bean's type.
\textbf{Bean EL Name:} Expression Language (EL) name for accessing the
bean in a JSP or JSF page.
\textbf{Qualifier:} Annotation applied to the bean for defining and
selecting a bean implementation.
\textbf{Valid during (phase):} The
\href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{portlet phases}
in which the bean is valid.
\section{Portlet Request Scoped
Beans}\label{portlet-request-scoped-beans}
These beans have the \texttt{@PortletRequestScoped} annotation. Here are
their artifact types, bean EL names, and annotation qualifiers, along
with their valid portlet phases.
Table 1: Portlet Request Scoped Beans\footnote{Martin Scott Nicklous,
Java™ Portlet Specification 3.0, page 122.}
\noindent\hrulefill
\begin{longtable}[]{@{}llll@{}}
\toprule\noalign{}
Artifact & Bean EL Name & Qualifier & Valid during \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{PortletConfig} & \texttt{portletConfig} & - & all \\
\texttt{PortletRequest} & \texttt{portletRequest} & - & all \\
\texttt{PortletResponse} & \texttt{portletResponse} & - & all \\
\texttt{ActionRequest} & \texttt{actionRequest} & - & action \\
\texttt{ActionResponse} & \texttt{actionResponse} & - & action \\
\texttt{HeaderRequest} & \texttt{headerRequest} & - & header \\
\texttt{HeaderResponse} & \texttt{headerResponse} & - & header \\
\texttt{RenderRequest} & \texttt{renderRequest} & - & render \\
\texttt{RenderResponse} & \texttt{renderResponse} & - & render \\
\texttt{EventRequest} & \texttt{eventRequest} & - & event \\
\texttt{EventResponse} & \texttt{eventResponse} & - & event \\
\texttt{ResourceRequest} & \texttt{resourceRequest} & - & resource \\
\texttt{ResourceResponse} & \texttt{resourceResponse} & - & resource \\
\texttt{StateAwareResponse} & \texttt{stateAwareResponse} & - & action,
event \\
\texttt{MimeResponse} & \texttt{mimeResponse} & - & header, render,
resource \\
\texttt{ClientDataRequest} & \texttt{clientDataRequest} & - & action,
resource \\
\texttt{RenderParameters} & \texttt{renderParams} & - & all \\
\texttt{MutableRenderParameters} & \texttt{mutableRenderParams} & - &
action, event \\
\texttt{ActionParameters} & \texttt{actionParams} & - & action \\
\texttt{ResourceParameters} & \texttt{resourceParams} & - & resource \\
\texttt{PortletContext} & \texttt{portletContext} & - & all \\
\texttt{PortletMode} & \texttt{portletMode} & - & all \\
\texttt{WindowState} & \texttt{windowState} & - & all \\
\texttt{PortletPreferences} & \texttt{portletPreferences} & - & all \\
\texttt{Cookies(List\textless{}Cookie\textgreater{})} & \texttt{cookies}
& - & all \\
\texttt{PortletSession} & \texttt{portletSession} & - & all \\
\texttt{Locales(List\textless{}Locale\textgreater{})} & \texttt{locales}
& - & all \\
\end{longtable}
\noindent\hrulefill
\section{Dependent Scoped Beans}\label{dependent-scoped-beans}
These beans use the \texttt{@Dependent} scope. They're of type
\texttt{java.lang.String}, which is \texttt{final}. This disqualifies
them from being proxied. To prevent using dependent scoped beans in a
scope broader than their original scope, you should only inject them
into \texttt{@PortletRequestScoped} beans.
Table 2: Dependent Scoped Beans\footnote{Martin Scott Nicklous, Java™
Portlet Specification 3.0, page 123.}
\noindent\hrulefill
\begin{longtable}[]{@{}llll@{}}
\toprule\noalign{}
Artifact & Bean EL Name & Qualifier & Valid during \\
\midrule\noalign{}
\endhead
\bottomrule\noalign{}
\endlastfoot
\texttt{Namespace} (String) & \texttt{namespace} & \texttt{@Namespace} &
all \\
\texttt{ContextPath} (String) & \texttt{contextPath} &
\texttt{@ContextPath} & all \\
\texttt{WindowID} (String) & \texttt{windowId} & \texttt{@WindowId} &
all \\
\texttt{Portlet\ name} (String) & \texttt{portletName} &
\texttt{@PortletName} & all \\
\end{longtable}
\noindent\hrulefill
\section{Related Topics}\label{related-topics-49}
\href{/docs/7-2/frameworks/-/knowledge_base/f/cdi-dependency-injection}{CDI
Dependency Injection}
\chapter{Item Selector Criterion and Return
Types}\label{item-selector-criterion-and-return-types}
Liferay DXP contains Item Selector criterion
(\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/ItemSelectorCriterion.html}{\texttt{ItemSelectorCriterion}})
and return type
(\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/ItemSelectorReturnType.html}{\texttt{ItemSelectorReturnType}})
classes that developers can use in Item Selectors. The following
sections in this document list the available classes:
\begin{itemize}
\tightlist
\item
\hyperref[item-selector-criterion-classes]{Criterion Classes}
\item
\hyperref[item-selector-return-type-classes]{Return Type Classes}
\end{itemize}
If there isn't a criterion or return type for your needs, you can create
your own by following the instructions in
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-criterion-and-return-types}{Creating
Custom Criterion and Return Types}. For more information on Item
Selectors in general, including definitions of criterion and return
types, see the
\href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item
Selector introduction}.
\section{Item Selector Criterion
Classes}\label{item-selector-criterion-classes}
\textbf{Assets:}
\href{https://docs.liferay.com/dxp/apps/asset/latest/javadocs/com/liferay/asset/display/page/item/selector/criterion/AssetDisplayPageSelectorCriterion.html}{AssetDisplayPageSelectorCriterion}:
Asset display page.
\textbf{Blogs:}
\href{https://docs.liferay.com/dxp/apps/blogs/latest/javadocs/com/liferay/blogs/item/selector/criterion/BlogsItemSelectorCriterion.html}{BlogsItemSelectorCriterion}:
Blogs item.
\textbf{Page Fragments:}
\href{https://docs.liferay.com/dxp/apps/fragment/latest/javadocs/com/liferay/fragment/item/selector/criterion/FragmentItemSelectorCriterion.html}{FragmentItemSelectorCriterion}:
\href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{Page
fragment}.
\textbf{Item Selector:}
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/audio/criterion/AudioItemSelectorCriterion.html}{AudioItemSelectorCriterion}:
Audio file.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/file/criterion/FileItemSelectorCriterion.html}{FileItemSelectorCriterion}:
Document Library file.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/image/criterion/ImageItemSelectorCriterion.html}{ImageItemSelectorCriterion}:
Image file.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/upload/criterion/UploadItemSelectorCriterion.html}{UploadItemSelectorCriterion}:
Uploadable file.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/url/criterion/URLItemSelectorCriterion.html}{URLItemSelectorCriterion}:
URL.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/video/criterion/VideoItemSelectorCriterion.html}{VideoItemSelectorCriterion}:
Video file.
\textbf{Journal (Web Content):}
\href{https://docs.liferay.com/dxp/apps/journal/latest/javadocs/com/liferay/journal/item/selector/criterion/JournalItemSelectorCriterion.html}{JournalItemSelectorCriterion}:
Web content article.
\textbf{Knowledge Base:}
\href{https://docs.liferay.com/dxp/apps/knowledge-base/latest/javadocs/com/liferay/knowledge/base/item/selector/criterion/KBAttachmentItemSelectorCriterion.html}{KBAttachmentItemSelectorCriterion}:
Knowledge base attachment.
\textbf{Layout:}
\href{https://docs.liferay.com/dxp/apps/layout/latest/javadocs/com/liferay/layout/item/selector/criterion/LayoutItemSelectorCriterion.html}{LayoutItemSelectorCriterion}:
Page layout.
\textbf{Organizations:}
\href{https://docs.liferay.com/dxp/apps/organizations/latest/javadocs/com/liferay/organizations/item/selector/OrganizationItemSelectorCriterion.html}{OrganizationItemSelectorCriterion}:
Organization.
\textbf{Roles:}
\href{https://docs.liferay.com/dxp/apps/roles/latest/javadocs/com/liferay/roles/item/selector/RoleItemSelectorCriterion.html}{RoleItemSelectorCriterion}:
Role.
\textbf{Site Navigation:}
\href{https://docs.liferay.com/dxp/apps/site-navigation/latest/javadocs/com/liferay/site/navigation/item/selector/criterion/SiteNavigationMenuItemItemSelectorCriterion.html}{SiteNavigationMenuItemItemSelectorCriterion}:
Site navigation menu item.
\href{https://docs.liferay.com/dxp/apps/site-navigation/latest/javadocs/com/liferay/site/navigation/item/selector/criterion/SiteNavigationMenuItemSelectorCriterion.html}{SiteNavigationMenuItemSelectorCriterion}:
Site navigation menu.
\textbf{Sites:}
\href{https://docs.liferay.com/dxp/apps/site/latest/javadocs/com/liferay/site/item/selector/criterion/SiteItemSelectorCriterion.html}{SiteItemSelectorCriterion}:
Site.
\textbf{User Groups Admin:}
\href{https://docs.liferay.com/dxp/apps/user-groups-admin/latest/javadocs/com/liferay/user/groups/admin/item/selector/UserGroupItemSelectorCriterion.html}{UserGroupItemSelectorCriterion}:
User group.
\textbf{Users Admin:}
\href{https://docs.liferay.com/dxp/apps/users-admin/latest/javadocs/com/liferay/users/admin/item/selector/UserItemSelectorCriterion.html}{UserItemSelectorCriterion}:
User.
\textbf{Wiki:}
\href{https://docs.liferay.com/dxp/apps/wiki/latest/javadocs/com/liferay/wiki/item/selector/criterion/WikiAttachmentItemSelectorCriterion.html}{WikiAttachmentItemSelectorCriterion}:
Wiki attachment.
\href{https://docs.liferay.com/dxp/apps/wiki/latest/javadocs/com/liferay/wiki/item/selector/criterion/WikiPageItemSelectorCriterion.html}{WikiPageItemSelectorCriterion}:
Wiki page.
\section{Item Selector Return Type
Classes}\label{item-selector-return-type-classes}
\textbf{Adaptive Media:}
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/item/selector/AMImageFileEntryItemSelectorReturnType.html}{AMImageFileEntryItemSelectorReturnType}:
Adaptive Media image file.
\href{https://docs.liferay.com/dxp/apps/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/item/selector/AMImageURLItemSelectorReturnType.html}{AMImageURLItemSelectorReturnType}:
Adaptive Media image URL.
\textbf{Item Selector:}
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/Base64ItemSelectorReturnType.html}{Base64ItemSelectorReturnType}:
The entity's Base64 encoding as a \texttt{String}.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/DownloadURLItemSelectorReturnType.html}{DownloadURLItemSelectorReturnType}:
The entity's download URL as a \texttt{String}.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/FileEntryItemSelectorReturnType.html}{FileEntryItemSelectorReturnType}:
File entry information as a JSON object.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/URLItemSelectorReturnType.html}{URLItemSelectorReturnType}:
The entity's URL as a \texttt{String}.
\href{https://docs.liferay.com/dxp/apps/item-selector/latest/javadocs/com/liferay/item/selector/criteria/UUIDItemSelectorReturnType.html}{UUIDItemSelectorReturnType}:
The entity's universally unique identifier (UUID) as a \texttt{String}.
\textbf{Site:}
\href{https://docs.liferay.com/dxp/apps/site/latest/javadocs/com/liferay/site/item/selector/criteria/SiteItemSelectorReturnType.html}{SiteItemSelectorReturnType}:
The Site's information as a JSON object.
\textbf{Wiki:}
\href{https://docs.liferay.com/dxp/apps/wiki/latest/javadocs/com/liferay/wiki/item/selector/WikiPageTitleItemSelectorReturnType.html}{WikiPageTitleItemSelectorReturnType}:
The wiki page's title.
\href{https://docs.liferay.com/dxp/apps/wiki/latest/javadocs/com/liferay/wiki/item/selector/WikiPageURLItemSelectorReturnType.html}{WikiPageURLItemSelectorReturnType}:
The wiki page's URL.
\chapter{Java APIs}\label{java-apis}
Here you'll find Javadoc for Liferay DXP and Liferay DXP apps. Note that
each link to the Javadoc listed here opens in a new window.
For help finding module attributes and configuring dependencies, see
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring
Dependencies}.
\section{7.0 Java APIs}\label{java-apis-1}
This table contains links to the Javadoc for 7.0 API modules. The root
location for these modules' Javadoc is
\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/}{here}.
Core
com.liferay.portal.kernel (portal-kernel): ~for developing applications
on Liferay DXP
com.liferay.util.bridges (util-bridges): ~for using various
non-proprietary computing languages, frameworks, and utilities on
Liferay DXP
com.liferay.util.java (util-java): ~for using various Java-related
frameworks and utilities on Liferay DXP
com.liferay.util.slf4j (util-slf4j): ~for using the Simple Logging
Facade for Java (SLF4J)
com.liferay.portal.impl (portal-impl): ~refer to this only if you are an
advanced Liferay developer that needs a deeper understanding of 7.0's
implementation in order to contribute to it
\section{Liferay DXP App Java APIs}\label{liferay-dxp-app-java-apis}
The tables in this section link to the API modules for apps in these
categories:
\begin{itemize}
\tightlist
\item
\hyperref[collaboration]{Collaboration}
\item
\hyperref[forms-and-workflow]{Forms and Workflow}
\item
\hyperref[foundation]{Foundation}
\item
\hyperref[web-experience]{Web Experience}
\end{itemize}
Note that the root location for these modules' Javadoc is
{[}https://docs.liferay.com/dxp/apps{]}( \#\# Collaboration
Announcements
com.liferay.announcements.api
Blogs
com.liferay.blogs.api
com.liferay.blogs.item.selector.api
com.liferay.blogs.recent.bloggers.api
Comment
com.liferay.comment.api
Document Library
com.liferay.document.library.api
com.liferay.document.library.content.api
com.liferay.document.library.file.rank.api
com.liferay.document.library.repository.authorization.api
com.liferay.document.library.repository.cmis.api
com.liferay.document.library.repository.external.api
com.liferay.document.library.sync.api
Flags
com.liferay.flags.api
Invitation
com.liferay.invitation.invite.members.api
Item Selector
com.liferay.item.selector.api
com.liferay.item.selector.criteria.api
Mentions
com.liferay.mentions.api
Message Boards
com.liferay.message.boards.api
Ratings
com.liferay.ratings.api
Reading Time
com.liferay.reading.time.api
Social
com.liferay.social.activities.api
com.liferay.social.activity.api
com.liferay.social.bookmarks.api
com.liferay.social.user.statistics.api
Subscription
com.liferay.subscription.api
Upload
com.liferay.upload.api
Wiki
com.liferay.wiki.api
\section{Forms and Workflow}\label{forms-and-workflow}
Calendar
com.liferay.calendar.api
Dynamic Data Lists
com.liferay.dynamic.data.lists.api
Dynamic Data Mapping
com.liferay.dynamic.data.mapping.api
Polls
com.liferay.polls.api
Portal Reports Engine
com.liferay.portal.reports.engine.api
Portal Rules Engine
com.liferay.portal.rules.engine.api
Portal Workflow
com.liferay.portal.workflow.api
com.liferay.portal.workflow.kaleo.api
com.liferay.portal.workflow.kaleo.definition.api
com.liferay.portal.workflow.kaleo.runtime.api
\section{Foundation}\label{foundation}
Captcha
com.liferay.captcha.api
Configuration Admin
com.liferay.configuration.admin.api
Contacts
com.liferay.contacts.api
Friendly URL
com.liferay.friendly.url.api
Frontend Editor
com.liferay.frontend.editor.api
Frontend Image Editor
com.liferay.frontend.image.editor.api
Frontend JS
com.liferay.frontend.js.loader.modules.extender.api
Map
com.liferay.map.api
Mobile Device Rules
com.liferay.mobile.device.rules.api
Organizations
com.liferay.organizations.api
com.liferay.organizations.item.selector.api
Password Policies Admin
com.liferay.password.policies.admin.api
Portal Background Task
com.liferay.portal.background.task.api
Portal Cache
com.liferay.portal.cache.api
Portal Configuration
com.liferay.portal.configuration.upgrade.api
Portal Instances
com.liferay.portal.instances.api
Portal Lock
com.liferay.portal.lock.api
Portal Remote
com.liferay.portal.remote.soap.extender.api
Portal Scripting
com.liferay.portal.scripting.api
Portal Search
com.liferay.portal.search.api
com.liferay.portal.search.engine.adapter.api
com.liferay.portal.search.web.api
Portal Security Audit
com.liferay.portal.security.audit.api
com.liferay.portal.security.audit.event.generators.api
com.liferay.portal.security.audit.storage.api
Portal Security SSO
com.liferay.portal.security.sso.cas.api
com.liferay.portal.security.sso.facebook.connect.api
com.liferay.portal.security.sso.ntlm.api
com.liferay.portal.security.sso.openid.api
com.liferay.portal.security.sso.openid.connect.api
com.liferay.portal.security.sso.opensso.api
com.liferay.portal.security.sso.token.api
Portal Security
com.liferay.portal.security.exportimport.api
com.liferay.portal.security.ldap.api
com.liferay.portal.security.permission.api
com.liferay.portal.security.service.access.policy.api
com.liferay.portal.security.service.access.quota.api
Portal Security SSO Google
com.liferay.portal.security.sso.google.api
Portal Settings
com.liferay.portal.settings.api
Portal Template
com.liferay.portal.template.soy.api
Portal URL Builder
com.liferay.portal.url.builder.api
Portal
com.liferay.portal.custom.jsp.bag.api
com.liferay.portal.dao.orm.custom.sql.api
com.liferay.portal.instance.lifecycle.api
com.liferay.portal.jmx.api
com.liferay.portal.output.stream.container.api
com.liferay.portal.spring.extender.api
com.liferay.portal.upgrade.api
Roles
com.liferay.roles.admin.api
com.liferay.roles.item.selector.api
Text Localizer
com.liferay.text.localizer.address.api
User-associated Data
com.liferay.user.associated.data.api
User Groups Admin
com.liferay.user.groups.admin.api
com.liferay.user.groups.admin.item.selector.api
Users Admin
com.liferay.users.admin.api
com.liferay.users.admin.item.selector.api
XStream
com.liferay.xstream.configurator.api
\section{Web Experience}\label{web-experience}
Application List
com.liferay.application.list.api
Asset
com.liferay.asset.api
com.liferay.asset.categories.navigation.api
com.liferay.asset.category.property.api
com.liferay.asset.display.api
com.liferay.asset.display.page.api
com.liferay.asset.display.page.item.selector.api
com.liferay.asset.entry.rel.api
com.liferay.asset.publisher.api
com.liferay.asset.tag.stats.api
com.liferay.asset.tags.api
com.liferay.asset.tags.navigation.api
Export Import
com.liferay.exportimport.api
com.liferay.exportimport.changeset.api
Fragment
com.liferay.fragment.api
com.liferay.fragment.item.selector.api
HTML Preview
com.liferay.html.preview.api
Journal
com.liferay.journal.api
com.liferay.journal.content.asset.addon.entry.api
com.liferay.journal.item.selector.api
Layout
com.liferay.layout.api
com.liferay.layout.admin.api
com.liferay.layout.item.selector.api
com.liferay.layout.page.template.api
com.liferay.layout.prototype.api
com.liferay.layout.set.prototype.api
Portlet Display Template
com.liferay.portlet.display.template.api
Product Navigation
com.liferay.product.navigation.control.menu.api
com.liferay.product.navigation.product.menu.api
com.liferay.product.navigation.simulation.api
RSS
com.liferay.rss.api
Site Navigation
com.liferay.site.navigation.api
com.liferay.site.navigation.admin.api
com.liferay.site.navigation.item.selector.api
com.liferay.site.navigation.language.api
Site
com.liferay.site.api
com.liferay.site.item.selector.api
Staging
com.liferay.staging.api
Trash
com.liferay.trash.api
\section{JavaScript and CSS}\label{javascript-and-css}
\href{https://liferay.design/lexicon/}{\textbf{Lexicon}}: A system for
building applications in and outside of Liferay DXP, designed to be
fluid and extensible, as well as provide a consistent and documented
API.
\href{https://clayui.com/}{\textbf{Clay}}: The web implementation of
Lexicon.
\href{http://getbootstrap.com/}{\textbf{Bootstrap}}: The base CSS
library onto which Lexicon is built. Liferay DXP uses Bootstrap natively
and all of its CSS classes and JavaScript features are available within
portlets, templates, and themes.
\href{http://alloyui.com}{\textbf{AlloyUI}}: AlloyUI and all of its
JavaScript APIs are available within portlets, templates, and themes.
\section{Descriptor Definitions}\label{descriptor-definitions}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/}{\textbf{DTDs}}:
Describes the XML files used in configuring Liferay DXP apps, 7.0
plugins, and Liferay DXP 7.2.
\chapter{Meaningful Schema
Versioning}\label{meaningful-schema-versioning}
Liferay's data schema version convention communicates a schema's
compatibility with older versions of the software. It tells you whether
a schema's changes maintain or break compatibility with existing
software. For example, if a new data schema removes a field your
software expects, the schema breaks compatibility. But if a new schema's
changes are non-breaking (e.g., adds a new field), the schema is
compatible and can be used with existing software.
Since Liferay DXP 7.1, Liferay uses a meaningful schema version
convention (similar to \href{http://semver.org}{Semantic Versioning}) to
define new
\href{/docs/7-2/frameworks/-/knowledge_base/f/creating-an-upgrade-process-for-your-app}{upgrade
steps} and support rollback of schema micro versions. The schema version
defines the status of the database schema and its data belonging to that
module or Core in a certain moment. The concept of schema versioning is
different from bundle versioning. The biggest concern in versioning a
data schema is \textbf{backward-compatibility} between the new schema
and the code that operates on the data.
Here's Liferay's schema version convention:
\textbf{MAJOR.MINOR.MICRO}
Each part means something:
\textbf{MAJOR:} Contains breaking schema/data changes that are
incompatible with the previous version of the code.
\textbf{MINOR:} Contains schema/data changes compatible with the
previous version of the code. The changes are required for the new
version of the code to work (the application will fail without applying
the schema/data changes)
\textbf{MICRO:} Contains schema/data changes that are compatible with
the previous version of the code. The changes are optional.
If you're not sure what kind of schema version change represents your
upgrade step, ask yourself these questions:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Will the previous code version (previous FP, SP, or GA) work with
these schema/data changes?
\begin{itemize}
\tightlist
\item
If not, it is a major change.
\item
If yes, continue.
\end{itemize}
\item
Are the schema/data changes required for the application to work?
(Obviously, all changes are intended to improve the application but in
some cases the application is fully functional without them)
\begin{itemize}
\tightlist
\item
If yes, it is a minor change.
\item
If not, it is a micro change.
\end{itemize}
\end{enumerate}
Next are some concrete examples of micro, minor, and major changes.
\section{Micro change examples}\label{micro-change-examples}
Here are common micro changes:
\begin{itemize}
\tightlist
\item
Increasing \texttt{VARCHAR} field sizes.
\item
Modifying DB indexes.
\item
Modifying data values to adapt to current logic. These include
backwards compatible data changes only. These changes commonly occur
when data updates are missed for new functionalities.
\item
Converting a field from a String to a CLOB, as long as the field has
few records and isn't used in \texttt{DISTINCT} or \texttt{GROUP\ BY}
SQL clauses.
\end{itemize}
\section{Minor change examples}\label{minor-change-examples}
Here are common minor changes:
\begin{itemize}
\tightlist
\item
Adding a new DB field.
\item
Adding a new DB table.
\end{itemize}
\textbf{Important:} The changes above are major if they require
modifying current existing data or extract information to populate the
new field or table. In such cases, the data can become incorrect if you
rolled back to the previous code version and then, after some time,
installed the new code again.
\section{Major change examples}\label{major-change-examples}
Here are common major changes:
\begin{itemize}
\tightlist
\item
Making data modifications that are not backward compatible.
\item
Removing a DB field
\item
Removing a DB table.
\item
Altering a column name.
\item
Decreasing the size of a \texttt{VARCHAR} field.
\item
Converting a field from a String to a CLOB, where the field is has
many records or is used in \texttt{DISTINCT} or \texttt{GROUP\ BY} SQL
clauses.
\item
Adding a new DB field or table that requires modifying current
existing data or extracts information to populate the new field or
table.
\end{itemize}
Now you can ascribe meaningful versions to your module's data schemas.
\chapter{Portlet 3.0 API Opt In}\label{portlet-3.0-api-opt-in}
A portlet must specify version 3.0 to ``opt in'' to the Portlet 3.0 API.
The 3.0 Portlet API version can be specified in the following ways.
\section{\texorpdfstring{Standard Portlet \texttt{@PortletApplication}
Annotation}{Standard Portlet @PortletApplication Annotation}}\label{standard-portlet-portletapplication-annotation}
Standard portlets need only specify the
\href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/PortletApplication.html}{\texttt{@PortletApplication}}
annotation.
\begin{verbatim}
@PortletApplication(version="3.0") // 3.0 is the default for this annotation attribute
@PortletConfiguration(portletName="myPortlet")
public class MyPortlet {
...
}
\end{verbatim}
\section{\texorpdfstring{Liferay MVC Portlet \texttt{@Component}
Annotation}{Liferay MVC Portlet @Component Annotation}}\label{liferay-mvc-portlet-component-annotation}
Declarative Services portlets, such as \texttt{MVCPortlet}, can specify
version 3.0 in their \texttt{@Component} annotation.
\begin{verbatim}
@Component(properties="javax.portlet.version=3.0", service=javax.portlet.Portlet.class)
public class MyDeclarativeServicesPortlet {
...
}
\end{verbatim}
\section{\texorpdfstring{\texttt{portlet.xml}
Descriptor}{portlet.xml Descriptor}}\label{portlet.xml-descriptor}
All portlets can specify version 3.0 in their \texttt{portlet.xml}
descriptor.
\begin{verbatim}
...
\end{verbatim}
\chapter{Portlet Descriptor to OSGi Service Property
Map}\label{portlet-descriptor-to-osgi-service-property-map}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
This article maps portlet XML descriptor values to OSGi service
properties for publishing OSGi Portlets.
OSGi service definitions can use properties. OSGi service properties
centralize and simplify portlet configuration. They are typically
represented as key-value pairs or, more generally, as a Map-like object.
Portlet spec property keys are prefixed by
\begin{verbatim}
javax.portlet.
\end{verbatim}
Liferay property keys are prefixed by
\begin{verbatim}
com.liferay.portlet.
\end{verbatim}
The mappings essentially flatten what is found in the XML descriptor.
The property names resemble the original descriptor names.
This article covers these descriptor mappings:
\begin{itemize}
\item
\hyperref[portlet-descriptor-mappings]{Portlet descriptor mappings}
\item
\hyperref[liferay-descriptor-mappings]{Liferay descriptor mappings}
\begin{itemize}
\item
\hyperref[liferay-display]{From \texttt{liferay-display.xml}}
\item
\hyperref[liferay-portlet]{From \texttt{liferay-portlet.xml}}
\end{itemize}
\end{itemize}
The standard portlet descriptor mappings are first.
\section{Portlet Descriptor Mappings}\label{portlet-descriptor-mappings}
\textbf{Note:} XPath notation derived from the \textbf{Portlet XSD}
\hyperref[four]{4} is used in this document for simplicity.
\noindent\hrulefill
portlet.xml XPath \textbar{} OSGi Portlet Service Property\textbar{}
\texttt{/portlet-app/container-runtime-option}\textbar not
supported\textbar{}
\texttt{/portlet-app/custom-portlet-mode}\textbar not
supported\textbar{}
\texttt{/portlet-app/custom-window-state}\textbar not
supported\textbar{}
\texttt{/portlet-app/default-namespace}\textbar{}\texttt{javax.portlet.default-namespace=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/event-definition}\textbar{}\texttt{javax.portlet.event-definition=\textless{}QNameLocalPart\textgreater{};\textless{}QNameURI\textgreater{}{[};\textless{}PayloadType\textgreater{}{]}{[},\textless{}AliasQNameLocalPart\textgreater{};\textless{}AliasQNameURI\textgreater{}{]}}
\hyperref[two]{2}\textbar{}
\texttt{/portlet-app/filter}\texttt{/portlet-app/filter/init-param/name}\texttt{/portlet-app/filter-mapping}\textbar{}\hyperref[three]{3}\texttt{javax.portlet.init-param.\textless{}name\textgreater{}=\textless{}value\textgreater{}}
\hyperref[three]{3}, \hyperref[nine]{9}\hyperref[three]{3}\textbar{}
\texttt{/portlet-app/public-render-parameter}\textbar not
supported\textbar{} \texttt{/portlet-app/resource-bundle}\textbar not
supported\textbar{}
\texttt{/portlet-app/security-constraint}\textbar not
supported\textbar{} \texttt{/portlet-app/user-attribute}\textbar not
supported\textbar{}
\texttt{/portlet-app/version}\textbar{}\texttt{javax.portlet.version=\textless{}value\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/async-supported}\textbar{}\texttt{javax.portlet.async-supported=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/cache-scope}\textbar not
supported\textbar{}
\texttt{/portlet-app/portlet/container-runtime-option}\textbar{}\texttt{javax.portlet.container-runtime-option.\textless{}name\textgreater{}=\textless{}value\textgreater{}}
\hyperref[two]{2}\textbar{}
\texttt{/portlet-app/portlet/dependency}\textbar{}\texttt{javax.portlet.dependency=\textless{}name\textgreater{};\textless{}scope\textgreater{};\textless{}version\textgreater{}}
\hyperref[two]{2}, \hyperref[six]{6}\textbar{}
\texttt{/portlet-app/portlet/description}\textbar{}\texttt{javax.portlet.description=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/display-name}\textbar{}\texttt{javax.portlet.display-name=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/expiration-cache}\textbar{}\texttt{javax.portlet.expiration-cache=\textless{}int\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/init-param/name}\textbar{}\texttt{javax.portlet.init-param.\textless{}name\textgreater{}=\textless{}value\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/listener}\textbar{}\texttt{javax.portlet.listener=\textless{}listener-class\textgreater{};\textless{}ordinal\textgreater{}}
\hyperref[two]{2},\hyperref[eight]{8}\textbar{}
\texttt{/portlet-app/portlet/multipart-config/file-size-threshold}\textbar{}\texttt{javax.portlet.multipart.file-size-threshold=\textless{}Integer\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/multipart-config/location}\textbar{}\texttt{javax.portlet.multipart.location=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/multipart-config/max-file-size}\textbar{}\texttt{javax.portlet.multipart.max-file-size=\textless{}Long\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/multipart-config/max-request-size}\textbar{}\texttt{javax.portlet.multipart.max-request-size=\textless{}Long\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/portlet-class}\textbar{}\hyperref[one]{1}\textbar{}
\texttt{/portlet-app/portlet/portlet-info/keywords}\textbar{}\texttt{javax.portlet.info.keywords=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/portlet-info/short-title}\textbar{}\texttt{javax.portlet.info.short-title=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/portlet-info/title}\textbar{}\texttt{javax.portlet.info.title=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/portlet-name}
\hyperref[ten]{10}\textbar{}\texttt{javax.portlet.name=\textless{}String\textgreater{}}
\hyperref[ten]{10}\textbar{}
\texttt{/portlet-app/portlet/portlet-preferences}\textbar{}\texttt{javax.portlet.preferences=\textless{}String\textgreater{}}OR\texttt{javax.portlet.preferences=classpath:\textless{}path\_to\_file\_in\_jar\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/portlet-preferences/preferences-validator}\textbar{}\texttt{javax.portlet.preferences-validator=\textless{}String\textgreater{}}
\hyperref[one]{1}\textbar{}
\texttt{/portlet-app/portlet/resource-bundle}\textbar{}\texttt{javax.portlet.resource-bundle=\textless{}String\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/security-role-ref}\textbar{}\texttt{javax.portlet.security-role-ref=\textless{}String\textgreater{}{[},\textless{}String\textgreater{}{]}}\hyperref[two]{2}\textbar{}
\texttt{/portlet-app/portlet/supported-locale}\textbar{}\texttt{javax.portlet.supported-locale=\textless{}String\textgreater{}}
\hyperref[two]{2}\textbar{}
\texttt{/portlet-app/portlet/supported-processing-event}\textbar{}\texttt{javax.portlet.supported-processing-event=\textless{}QNameLocalPart\textgreater{}}
OR
\texttt{javax.portlet.supported-processing-event=\textless{}QNameLocalPart\textgreater{};\textless{}QNameURI\textgreater{}}
\hyperref[two]{2}\textbar{}
\texttt{/portlet-app/portlet/supported-public-render-parameter}\textbar{}\texttt{javax.portlet.supported-public-render-parameter=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/portlet-app/portlet/supported-publishing-event}\textbar{}\texttt{javax.portlet.supported-publishing-event=\textless{}QNameLocalPart\textgreater{}}
OR
\texttt{javax.portlet.supported-publishing-event=\textless{}QNameLocalPart\textgreater{};\textless{}QNameURI\textgreater{}}
\hyperref[two]{2}\textbar{}
\texttt{/portlet-app/portlet/supports/mime-type}\textbar{}\texttt{javax.portlet.mime-type=\textless{}mime-type\textgreater{}}\textbar{}
\texttt{/portlet-app/portlet/supports/portlet-mode}\textbar{}\texttt{javax.portlet.portlet-mode=\textless{}mime-type\textgreater{};\textless{}portlet-mode\textgreater{}{[},\textless{}portlet-mode\textgreater{}{]}*}\textbar{}
\texttt{/portlet-app/portlet/supports/window-state}\textbar{}\texttt{javax.portlet.window-state=\textless{}mime-type\textgreater{};\textless{}window-state\textgreater{}{[},\textless{}window-state\textgreater{}{]}*}\textbar{}
\noindent\hrulefill
\section{Liferay Descriptor Mappings}\label{liferay-descriptor-mappings}
\section{Liferay Display}\label{liferay-display}
\noindent\hrulefill
liferay-display.xml XPath \textbar{} OSGi Portlet Service
Property\textbar{}
\texttt{/display/category{[}@name{]}}\textbar{}\texttt{com.liferay.portlet.display-category=\textless{}value\textgreater{}}\textbar{}
\noindent\hrulefill
\section{Liferay Portlet}\label{liferay-portlet}
\textbf{Note:} XPath notation derived from \textbf{Liferay Portlet}
\hyperref[five]{5} is used in this document for simplicity.
\noindent\hrulefill
liferay-portlet.xml XPath \textbar{} OSGi Liferay Portlet Service
Property\textbar{}
\texttt{/liferay-portlet-app/portlet/action-timeout}\textbar{}\texttt{com.liferay.portlet.action-timeout=\textless{}int\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/action-url-redirect}\textbar{}\texttt{com.liferay.portlet.action-url-redirect=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/active}\textbar{}\texttt{com.liferay.portlet.active=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/add-default-resource}\textbar{}\texttt{com.liferay.portlet.add-default-resource=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/ajaxable}\textbar{}\texttt{com.liferay.portlet.ajaxable=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/asset-renderer-factory}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/atom-collection-adapter}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/autopropagated-parameters}\textbar{}\texttt{com.liferay.portlet.autopropagated-parameters=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/configuration-action-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/configuration-path}\textbar{}\texttt{com.liferay.portlet.configuration-path=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/control-panel-entry-category}\textbar{}\texttt{com.liferay.portlet.control-panel-entry-category=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/control-panel-entry-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/control-panel-entry-weight}\textbar{}\texttt{com.liferay.portlet.control-panel-entry-weight=\textless{}double\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/css-class-wrapper}\textbar{}\texttt{com.liferay.portlet.css-class-wrapper=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/custom-attributes-display}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/ddm-display}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/facebook-integration}\textbar{}\texttt{com.liferay.portlet.facebook-integration=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/footer-portal-css}\textbar{}\texttt{com.liferay.portlet.footer-portal-css=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/footer-portal-javascript}\textbar{}\texttt{com.liferay.portlet.footer-portal-javascript=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/footer-portlet-css}\textbar{}\texttt{com.liferay.portlet.footer-portlet-css=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/footer-portlet-javascript}\textbar{}\texttt{com.liferay.portlet.footer-portlet-javascript=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/friendly-url-mapper-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/friendly-url-mapping}\textbar{}\texttt{com.liferay.portlet.friendly-url-mapping=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/friendly-url-routes}\textbar{}\texttt{com.liferay.portlet.friendly-url-routes=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/header-portal-css}\textbar{}\texttt{com.liferay.portlet.header-portal-css=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/header-portal-javascript}\textbar{}\texttt{com.liferay.portlet.header-portal-javascript=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/header-portlet-css}\textbar{}\texttt{com.liferay.portlet.header-portlet-css=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/header-portlet-javascript}\textbar{}\texttt{com.liferay.portlet.header-portlet-javascript=\textless{}String\textgreater{}}\hyperref[two]{2}\textbar{}
\texttt{/liferay-portlet-app/portlet/header-request-attribute-prefix}\textbar{}\texttt{com.liferay.portlet.header-request-attribute-prefix=\textless{}String\textgreater{}}
\hyperref[seven]{7}\textbar{}
\texttt{/liferay-portlet-app/portlet/header-timeout}\textbar{}\texttt{header-timeout=\textless{}int\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/icon}\textbar{}\texttt{com.liferay.portlet.icon=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/include}\textbar not
supported\textbar{}
\texttt{/liferay-portlet-app/portlet/indexer-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/instanceable}\textbar{}\texttt{com.liferay.portlet.instanceable=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/layout-cacheable}\textbar{}\texttt{com.liferay.portlet.layout-cacheable=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/maximize-edit}\textbar{}\texttt{com.liferay.portlet.maximize-edit=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/maximize-help}\textbar{}\texttt{com.liferay.portlet.maximize-help=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/open-search-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/parent-struts-path}\textbar{}\texttt{com.liferay.portlet.parent-struts-path=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/permission-propagator}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/poller-processor-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/pop-message-listener-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/pop-up-print}\textbar{}\texttt{com.liferay.portlet.pop-up-print=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/portlet-data-handler-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/portlet-layout-listener-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/portlet-name}\textbar not
supported\textbar{}
\texttt{/liferay-portlet-app/portlet/portlet-url-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/preferences-company-wide}\textbar{}\texttt{com.liferay.portlet.preferences-company-wide=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/preferences-owned-by-group}\textbar{}\texttt{com.liferay.portlet.preferences-owned-by-group=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/preferences-unique-per-layout}\textbar{}\texttt{com.liferay.portlet.preferences-unique-per-layout=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/private-request-attributes}\textbar{}\texttt{com.liferay.portlet.private-request-attributes=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/private-session-attributes}\textbar{}\texttt{com.liferay.portlet.private-session-attributes=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/remoteable}\textbar{}\texttt{com.liferay.portlet.remoteable=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/render-timeout}\textbar{}\texttt{com.liferay.portlet.render-timeout=\textless{}int\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/render-weight}\textbar{}\texttt{com.liferay.portlet.render-weight=\textless{}int\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/requires-namespaced-parameters}\textbar{}\texttt{com.liferay.portlet.requires-namespaced-parameters=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/restore-current-view}\textbar{}\texttt{com.liferay.portlet.restore-current-view=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/scheduler-entry}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/scopeable}\textbar{}\texttt{com.liferay.portlet.scopeable=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/show-portlet-access-denied}\textbar{}\texttt{com.liferay.portlet.show-portlet-access-denied=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/show-portlet-inactive}\textbar{}\texttt{com.liferay.portlet.show-portlet-inactive=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/single-page-application}\textbar{}\texttt{com.liferay.portlet.single-page-application=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/social-activity-interpreter-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/social-request-interpreter-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/social-interactions-configuration}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/staged-model-data-handler-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/struts-path}\textbar{}\texttt{com.liferay.portlet.struts-path=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/system}\textbar{}\texttt{com.liferay.portlet.system=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/template-handler}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/trash-handler}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/url-encoder-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/use-default-template}\textbar{}\texttt{com.liferay.portlet.use-default-template=\textless{}boolean\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/user-notification-definitions}\textbar not
supported\textbar{}
\texttt{/liferay-portlet-app/portlet/user-notification-handler-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/user-principal-strategy}\textbar{}\texttt{com.liferay.portlet.user-principal-strategy=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/virtual-path}\textbar{}\texttt{com.liferay.portlet.virtual-path=\textless{}String\textgreater{}}\textbar{}
\texttt{/liferay-portlet-app/portlet/webdav-storage-class}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/webdav-storage-token}\textbar not
supported\textbar{}
\texttt{/liferay-portlet-app/portlet/workflow-handler}\textbar{}\hyperref[three]{3}\textbar{}
\texttt{/liferay-portlet-app/portlet/xml-rpc-method-class}\textbar{}\hyperref[three]{3}\textbar{}
\noindent\hrulefill
\begin{itemize}
\item
{[}1{]} Portlets are registered as concrete objects.
\item
{[}2{]} Multiples of these properties may be used. This results in an
array of values.
\item
{[}3{]} This type is registered as an OSGi service.
\item
{[}4{]} https://xmlns.jcp.org/xml/ns/portlet/portlet-app\_3\_0.xsd
\item
{[}5{]}
\href{https://docs.liferay.com/dxp/portal/7.2-latest/definitions/liferay-portlet-app_7_2_0.dtd.html}{http://www.liferay.com/dtd/liferay-portlet-app\_7\_2\_0.dtd}
\item
{[}6{]} Here's an example of using multiple
\texttt{javax.portlet.dependency} properties.
\emph{Old:}
\begin{verbatim}
...
jquery
com.jquery
2.1.1
jsutil
com.mycompany
1.0.0
...
\end{verbatim}
\emph{New:}
\begin{verbatim}
@Component(
immediate = true, property = {
"javax.portlet.name=my_portlet",
"javax.portlet.display-name=my-portlet",
"javax.portlet.dependency=jquery;com.jquery;2.1.1",
"javax.portlet.dependency=jsutil;com.mycompany;1.0.0"
}, service = Portlet.class
)
public class MyPortlet extends GenericPortlet {
...
}
\end{verbatim}
\item
{[}7{]} Here's an example for the
\texttt{com.liferay.portlet.header-request-attribute-prefix} property.
\emph{Old:}
\begin{verbatim}
...
com.mycompany
...
\end{verbatim}
\emph{New:}
\begin{verbatim}
@Component(
immediate = true, property = {
"javax.portlet.name=my_portlet",
"javax.portlet.display-name=my-portlet",
"javax.portlet.dependency=jquery;com.jquery;2.1.1",
"javax.portlet.dependency=jsutil;com.mycompany;1.0.0",
"com.liferay.portlet.header-request-attribute-prefix=com.mycompany"
}, service = Portlet.class
)
public class MyPortlet extends GenericPortlet {
...
}
\end{verbatim}
\item
{[}8{]} Here's an example for the \texttt{javax.portlet.listener}
property.
\emph{Old:}
\begin{verbatim}
...
com.mycompany.MyPortletURLGenerationListener
1
...
\end{verbatim}
\emph{New:}
\begin{verbatim}
@Component(
immediate = true,
property = {"javax.portlet.name=myPortlet",
"javax.portlet.listener=com.mycompany.MyPortletURLGenerationListener;1"
}, service = Portlet.class
)
public class MyPortlet extends GenericPortlet {
...
}
\end{verbatim}
\item
{[}9{]} An \texttt{javax.portlet.init-param} property can be declared
like this:
\begin{verbatim}
@Component(
immediate = true,
property = {"javax.portlet.name=myPortlet",
"javax.portlet.init-param.myInitParam=1234"},
service = PortletFilter.class
)
public class MyFilter implements RenderFilter {
...
}
\end{verbatim}
\item
{[}10{]} Liferay DXP creates each portlet's ID based on the portlet's
name (i.e., the \texttt{portlet-name} descriptor in
\texttt{liferay-portlet.xml} or the \texttt{javax.portlet.name} OSGi
service property). Dashes, periods, and spaces are allowed in the
portlet name, but they and all other JavaScript unsafe characters are
stripped from the name value that's used for the portlet ID.
Therefore, make your portlet name unique in light of the characters
that are removed. Otherwise, if you try to deploy a portlet whose ID
is the same as a portlet that's already deployed, your portlet
deployment fails and Liferay DXP logs a message like this:
\begin{verbatim}
Portlet id [portletId] is already in use
\end{verbatim}
\end{itemize}
\chapter{Third Party Packages Portal
Exports}\label{third-party-packages-portal-exports}
{ This document has been updated and ported to Liferay Learn and is no
longer maintained here.}
The \texttt{com.liferay.portal.bootstrap} module exports many third
party Java packages that can cause problems if used improperly. If your
WAR's Gradle file, for example, uses the \texttt{compile} scope for a
\href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{dependency}
that Liferay's OSGi runtime already provides, the dependency JAR is
included in the WAR's \texttt{WEB-INF/lib} and deployed in the resulting
WAB, and two versions of dependency classes wind up on the classpath.
This can cause weird errors that are hard to debug.
To find a list of the packages exported by
\texttt{com.liferay.portal.bootstrap}, go to the source file
\texttt{modules/core/portal-bootstrap/system.packages.extra.bnd}. If you
don't have access to the source code, the same list (in a less
user-friendly format) is in the
\texttt{META-INF/system.packages.extra.mf} file in
\texttt{{[}LIFERAY\_HOME{]}/osgi/core/com.liferay.portal.bootstrap.jar}.
These packages are installed and available in Liferay's OSGi runtime. If
your module or WAR uses one of them, specify the corresponding
dependency as being ``provided'' (provided by Liferay DXP). Here's how
to specify a provided dependency:
Maven:
\texttt{\textless{}scope\textgreater{}provided\textless{}/scope\textgreater{}}
Gradle: \texttt{providedCompile}
Now you can safely leverage third party packages Liferay DXP provides!
================================================
FILE: book/developer/tutorials.aux
================================================
\relax
\providecommand{\transparent@use}[1]{}
\providecommand\hyper@newdestlabel[2]{}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {1}Developer Tutorials}{3}{chapter.1}\protected@file@percent }
\newlabel{developer-tutorials}{{1}{3}{Developer Tutorials}{chapter.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {2}Developing a Web Application}{5}{chapter.2}\protected@file@percent }
\newlabel{developing-a-web-application}{{2}{5}{Developing a Web Application}{chapter.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.1}{\ignorespaces It looks humble, but there's a lot of functionality packed in this application.}}{5}{figure.2.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {3}Setting Up a Development Environment}{7}{chapter.3}\protected@file@percent }
\newlabel{setting-up-a-development-environment}{{3}{7}{Setting Up a Development Environment}{chapter.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {3.1}Installing a Liferay Dev Studio DXP Bundle}{7}{section.3.1}\protected@file@percent }
\newlabel{installing-a-liferay-dev-studio-dxp-bundle}{{3.1}{7}{Installing a Liferay Dev Studio DXP Bundle}{section.3.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {3.2}Creating a Liferay Workspace}{8}{section.3.2}\protected@file@percent }
\newlabel{creating-a-liferay-workspace}{{3.2}{8}{Creating a Liferay Workspace}{section.3.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {3.1}{\ignorespaces By selecting \emph {Liferay Workspace}, you begin the process of creating a new workspace for your Liferay DXP projects.}}{8}{figure.3.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {4}Generating the Back-end}{11}{chapter.4}\protected@file@percent }
\newlabel{generating-the-back-end}{{4}{11}{Generating the Back-end}{chapter.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {4.1}{\ignorespaces Service Builder generates the shaded layers of your application.}}{11}{figure.4.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {5}What is Service Builder?}{13}{chapter.5}\protected@file@percent }
\newlabel{what-is-service-builder}{{5}{13}{What is Service Builder?}{chapter.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {5.1}Guestbook Application Design}{13}{section.5.1}\protected@file@percent }
\newlabel{guestbook-application-design}{{5.1}{13}{Guestbook Application Design}{section.5.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {5.1}{\ignorespaces When you're done, the Guestbook supports multiple guestbooks and makes use of many Liferay features.}}{13}{figure.5.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {5.2}Service Layer}{14}{section.5.2}\protected@file@percent }
\newlabel{service-layer}{{5.2}{14}{Service Layer}{section.5.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {5.2}{\ignorespaces Your current project structure.}}{14}{figure.5.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {6}Generating Model, Service, and Persistence Layers}{15}{chapter.6}\protected@file@percent }
\newlabel{generating-model-service-and-persistence-layers}{{6}{15}{Generating Model, Service, and Persistence Layers}{chapter.6}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {6.1}{\ignorespaces The Model, Service, and Persistence Layer comprise a loose coupling design.}}{19}{figure.6.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {7}Implementing Service Methods}{21}{chapter.7}\protected@file@percent }
\newlabel{implementing-service-methods}{{7}{21}{Implementing Service Methods}{chapter.7}{}}
\@writefile{toc}{\contentsline {section}{\numberline {7.1}Updating Generated Classes}{25}{section.7.1}\protected@file@percent }
\newlabel{updating-generated-classes}{{7.1}{25}{Updating Generated Classes}{section.7.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {8}Building the Web Front-End}{27}{chapter.8}\protected@file@percent }
\newlabel{building-the-web-front-end}{{8}{27}{Building the Web Front-End}{chapter.8}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {9}Creating the Web Project}{29}{chapter.9}\protected@file@percent }
\newlabel{creating-the-web-project}{{9}{29}{Creating the Web Project}{chapter.9}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {9.1}{\ignorespaces Complete the New Module Project wizard.}}{30}{figure.9.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {9.2}{\ignorespaces After you move it, all of your modules are in the same folder..}}{31}{figure.9.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {9.1}What is a Portlet?}{31}{section.9.1}\protected@file@percent }
\newlabel{what-is-a-portlet}{{9.1}{31}{What is a Portlet?}{section.9.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {9.2}What is a Component?}{31}{section.9.2}\protected@file@percent }
\newlabel{what-is-a-component}{{9.2}{31}{What is a Component?}{section.9.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {9.3}{\ignorespaces Many Liferay applications can run at the same time on the same page.}}{32}{figure.9.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {9.3}Deploying the Application}{32}{section.9.3}\protected@file@percent }
\newlabel{deploying-the-application}{{9.3}{32}{Deploying the Application}{section.9.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {9.4}{\ignorespaces Drag and drop the module.}}{34}{figure.9.4}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {9.5}{\ignorespaces This is your new page with the Guestbook application that you created.}}{35}{figure.9.5}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {10}Defining the Component Metadata Properties}{37}{chapter.10}\protected@file@percent }
\newlabel{defining-the-component-metadata-properties}{{10}{37}{Defining the Component Metadata Properties}{chapter.10}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {10.1}{\ignorespaces Users choose applications from a list of display categories.}}{38}{figure.10.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {11}Creating Portlet Keys}{41}{chapter.11}\protected@file@percent }
\newlabel{creating-portlet-keys}{{11}{41}{Creating Portlet Keys}{chapter.11}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {12}Integrating the Back-end}{43}{chapter.12}\protected@file@percent }
\newlabel{integrating-the-back-end}{{12}{43}{Integrating the Back-end}{chapter.12}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {13}Creating an Add Entry Button}{45}{chapter.13}\protected@file@percent }
\newlabel{creating-an-add-entry-button}{{13}{45}{Creating an Add Entry Button}{chapter.13}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {13.1}{\ignorespaces Your new button is awesome, but it doesn't work yet.}}{46}{figure.13.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {14}Generating Portlet URLs}{47}{chapter.14}\protected@file@percent }
\newlabel{generating-portlet-urls}{{14}{47}{Generating Portlet URLs}{chapter.14}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {15}Linking to Another Page}{49}{chapter.15}\protected@file@percent }
\newlabel{linking-to-another-page}{{15}{49}{Linking to Another Page}{chapter.15}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {16}Forms and Action URLs}{51}{chapter.16}\protected@file@percent }
\newlabel{forms-and-action-urls}{{16}{51}{Forms and Action URLs}{chapter.16}{}}
\@writefile{toc}{\contentsline {section}{\numberline {16.1}Action URLs}{51}{section.16.1}\protected@file@percent }
\newlabel{action-urls}{{16.1}{51}{Action URLs}{section.16.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {16.2}Forms}{51}{section.16.2}\protected@file@percent }
\newlabel{forms}{{16.2}{51}{Forms}{section.16.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {16.1}{\ignorespaces This is the Guestbook application's form for adding entries.}}{52}{figure.16.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {17}Implementing Portlet Actions}{53}{chapter.17}\protected@file@percent }
\newlabel{implementing-portlet-actions}{{17}{53}{Implementing Portlet Actions}{chapter.17}{}}
\@writefile{toc}{\contentsline {section}{\numberline {17.1}Creating an Add Entry Action}{53}{section.17.1}\protected@file@percent }
\newlabel{creating-an-add-entry-action}{{17.1}{53}{Creating an Add Entry Action}{section.17.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {17.2}Creating a Delete Entry Action}{55}{section.17.2}\protected@file@percent }
\newlabel{creating-a-delete-entry-action}{{17.2}{55}{Creating a Delete Entry Action}{section.17.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {18}Displaying Guestbook Entries}{57}{chapter.18}\protected@file@percent }
\newlabel{displaying-guestbook-entries}{{18}{57}{Displaying Guestbook Entries}{chapter.18}{}}
\@writefile{toc}{\contentsline {section}{\numberline {18.1}Rendering the Portlet}{57}{section.18.1}\protected@file@percent }
\newlabel{rendering-the-portlet}{{18.1}{57}{Rendering the Portlet}{section.18.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {18.2}Displaying Guestbook Entries}{58}{section.18.2}\protected@file@percent }
\newlabel{displaying-guestbook-entries-1}{{18.2}{58}{Displaying Guestbook Entries}{section.18.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {18.3}Creating an Actions JSP}{60}{section.18.3}\protected@file@percent }
\newlabel{creating-an-actions-jsp}{{18.3}{60}{Creating an Actions JSP}{section.18.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {18.1}{\ignorespaces You have a form to enter information.}}{61}{figure.18.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {18.2}{\ignorespaces Submitted entries are displayed here.}}{61}{figure.18.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {19}Fitting it All Together}{63}{chapter.19}\protected@file@percent }
\newlabel{fitting-it-all-together}{{19}{63}{Fitting it All Together}{chapter.19}{}}
\@writefile{toc}{\contentsline {section}{\numberline {19.1}The Back-End}{63}{section.19.1}\protected@file@percent }
\newlabel{the-back-end}{{19.1}{63}{The Back-End}{section.19.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {19.1}{\ignorespaces Service Builder makes generating your database entities and your Java objects a snap.}}{63}{figure.19.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {19.2}The Front-End}{64}{section.19.2}\protected@file@percent }
\newlabel{the-front-end}{{19.2}{64}{The Front-End}{section.19.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {19.3}Deploying and Testing the Application}{64}{section.19.3}\protected@file@percent }
\newlabel{deploying-and-testing-the-application}{{19.3}{64}{Deploying and Testing the Application}{section.19.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {19.4}What's Next?}{64}{section.19.4}\protected@file@percent }
\newlabel{whats-next}{{19.4}{64}{What's Next?}{section.19.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {19.2}{\ignorespaces The controller directs page flow in an MVC Portlet application.}}{65}{figure.19.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {19.3}{\ignorespaces Your first guestbook and entry appears. Nice job!}}{66}{figure.19.3}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {20}Writing an Administrative Portlet}{67}{chapter.20}\protected@file@percent }
\newlabel{writing-an-administrative-portlet}{{20}{67}{Writing an Administrative Portlet}{chapter.20}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {20.1}{\ignorespaces The Guestbook Admin portlet lets administrators manage Guestbooks.}}{67}{figure.20.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {21}Creating the Classes}{69}{chapter.21}\protected@file@percent }
\newlabel{creating-the-classes}{{21}{69}{Creating the Classes}{chapter.21}{}}
\@writefile{toc}{\contentsline {section}{\numberline {21.1}Panels and Categories}{69}{section.21.1}\protected@file@percent }
\newlabel{panels-and-categories}{{21.1}{69}{Panels and Categories}{section.21.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {21.1}{\ignorespaces The product menu is split into three sections: the Control Panel, the User menu, and the Sites menu.}}{70}{figure.21.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {22}Adding Metadata}{71}{chapter.22}\protected@file@percent }
\newlabel{adding-metadata}{{22}{71}{Adding Metadata}{chapter.22}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {23}Updating Your Service Layer}{75}{chapter.23}\protected@file@percent }
\newlabel{updating-your-service-layer}{{23}{75}{Updating Your Service Layer}{chapter.23}{}}
\@writefile{toc}{\contentsline {section}{\numberline {23.1}Adding Guestbook Service Methods}{75}{section.23.1}\protected@file@percent }
\newlabel{adding-guestbook-service-methods}{{23.1}{75}{Adding Guestbook Service Methods}{section.23.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {24}Defining Portlet Actions}{77}{chapter.24}\protected@file@percent }
\newlabel{defining-portlet-actions}{{24}{77}{Defining Portlet Actions}{chapter.24}{}}
\@writefile{toc}{\contentsline {section}{\numberline {24.1}Adding Three Portlet Actions}{77}{section.24.1}\protected@file@percent }
\newlabel{adding-three-portlet-actions}{{24.1}{77}{Adding Three Portlet Actions}{section.24.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {25}Adding Tabs to the Guestbook Portlet}{81}{chapter.25}\protected@file@percent }
\newlabel{adding-tabs-to-the-guestbook-portlet}{{25}{81}{Adding Tabs to the Guestbook Portlet}{chapter.25}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {25.1}{\ignorespaces Users can click a tab to choose which Guestbook to sign.}}{81}{figure.25.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {26}Creating a User Interface}{83}{chapter.26}\protected@file@percent }
\newlabel{creating-a-user-interface}{{26}{83}{Creating a User Interface}{chapter.26}{}}
\@writefile{toc}{\contentsline {section}{\numberline {26.1}Step 1: Creating the Default View}{83}{section.26.1}\protected@file@percent }
\newlabel{step-1-creating-the-default-view}{{26.1}{83}{Step 1: Creating the Default View}{section.26.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {26.2}Step 2: Creating the Actions Button}{84}{section.26.2}\protected@file@percent }
\newlabel{step-2-creating-the-actions-button}{{26.2}{84}{Step 2: Creating the Actions Button}{section.26.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {26.3}Step 3: Creating the Edit Guestbook JSP}{86}{section.26.3}\protected@file@percent }
\newlabel{step-3-creating-the-edit-guestbook-jsp}{{26.3}{86}{Step 3: Creating the Edit Guestbook JSP}{section.26.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {26.1}{\ignorespaces The Guestbook Admin portlet can add or edit guestbooks, configure their permissions, or delete them.}}{87}{figure.26.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {27}Displaying Messages and Errors}{89}{chapter.27}\protected@file@percent }
\newlabel{displaying-messages-and-errors}{{27}{89}{Displaying Messages and Errors}{chapter.27}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {27.1}{\ignorespaces You can use Liferay's APIs to display helpful messages.}}{89}{figure.27.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {28}Creating Language Keys}{91}{chapter.28}\protected@file@percent }
\newlabel{creating-language-keys}{{28}{91}{Creating Language Keys}{chapter.28}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {29}Adding Failure and Success Messages}{93}{chapter.29}\protected@file@percent }
\newlabel{adding-failure-and-success-messages}{{29}{93}{Adding Failure and Success Messages}{chapter.29}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {30}Adding Messages to JSPs}{95}{chapter.30}\protected@file@percent }
\newlabel{adding-messages-to-jsps}{{30}{95}{Adding Messages to JSPs}{chapter.30}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {30.1}{\ignorespaces Now the message displays the value you specified in \texttt {Language.properties}.}}{95}{figure.30.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {31}Using Resources and Permissions}{97}{chapter.31}\protected@file@percent }
\newlabel{using-resources-and-permissions}{{31}{97}{Using Resources and Permissions}{chapter.31}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {32}Defining Permissions}{99}{chapter.32}\protected@file@percent }
\newlabel{defining-permissions}{{32}{99}{Defining Permissions}{chapter.32}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {32.1}{\ignorespaces Portlet permissions and resource permissions cover different parts of the application.}}{100}{figure.32.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {32.1}Defining Model Permissions}{101}{section.32.1}\protected@file@percent }
\newlabel{defining-model-permissions}{{32.1}{101}{Defining Model Permissions}{section.32.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {32.2}Defining Portlet Permissions}{103}{section.32.2}\protected@file@percent }
\newlabel{defining-portlet-permissions}{{32.2}{103}{Defining Portlet Permissions}{section.32.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {33}Registering Your Permissions in the Database}{105}{chapter.33}\protected@file@percent }
\newlabel{registering-your-permissions-in-the-database}{{33}{105}{Registering Your Permissions in the Database}{chapter.33}{}}
\@writefile{toc}{\contentsline {section}{\numberline {33.1}Registering Guestbook Resources}{105}{section.33.1}\protected@file@percent }
\newlabel{registering-guestbook-resources}{{33.1}{105}{Registering Guestbook Resources}{section.33.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {33.2}Registering Guestbook Entry Resources}{106}{section.33.2}\protected@file@percent }
\newlabel{registering-guestbook-entry-resources}{{33.2}{106}{Registering Guestbook Entry Resources}{section.33.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {34}Registering Permissions with the Container}{107}{chapter.34}\protected@file@percent }
\newlabel{registering-permissions-with-the-container}{{34}{107}{Registering Permissions with the Container}{chapter.34}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {35}Assigning Permissions to Resources}{111}{chapter.35}\protected@file@percent }
\newlabel{assigning-permissions-to-resources}{{35}{111}{Assigning Permissions to Resources}{chapter.35}{}}
\@writefile{toc}{\contentsline {section}{\numberline {35.1}Creating a Guestbook Portlet Permission Helper}{111}{section.35.1}\protected@file@percent }
\newlabel{creating-a-guestbook-portlet-permission-helper}{{35.1}{111}{Creating a Guestbook Portlet Permission Helper}{section.35.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {35.2}Creating Model Permission Helpers}{112}{section.35.2}\protected@file@percent }
\newlabel{creating-model-permission-helpers}{{35.2}{112}{Creating Model Permission Helpers}{section.35.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {36}Checking for Permission in JSPs}{115}{chapter.36}\protected@file@percent }
\newlabel{checking-for-permission-in-jsps}{{36}{115}{Checking for Permission in JSPs}{chapter.36}{}}
\@writefile{toc}{\contentsline {section}{\numberline {36.1}Checking Permissions in the UI}{115}{section.36.1}\protected@file@percent }
\newlabel{checking-permissions-in-the-ui}{{36.1}{115}{Checking Permissions in the UI}{section.36.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {36.2}Testing the Application}{117}{section.36.2}\protected@file@percent }
\newlabel{testing-the-application}{{36.2}{117}{Testing the Application}{section.36.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {37}Search and Indexing}{119}{chapter.37}\protected@file@percent }
\newlabel{search-and-indexing}{{37}{119}{Search and Indexing}{chapter.37}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {37.1}{\ignorespaces Add a search bar so users can search for Guestbook Entries. If a message or name matches the search query, the Entry is displayed in the search results.}}{120}{figure.37.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {38}Enabling Search and Indexing for Guestbooks}{121}{chapter.38}\protected@file@percent }
\newlabel{enabling-search-and-indexing-for-guestbooks}{{38}{121}{Enabling Search and Indexing for Guestbooks}{chapter.38}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {39}Understanding Search and Indexing}{123}{chapter.39}\protected@file@percent }
\newlabel{understanding-search-and-indexing}{{39}{123}{Understanding Search and Indexing}{chapter.39}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {40}Registering Guestbooks with the Search Framework}{125}{chapter.40}\protected@file@percent }
\newlabel{registering-guestbooks-with-the-search-framework}{{40}{125}{Registering Guestbooks with the Search Framework}{chapter.40}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {41}Indexing Guestbooks}{127}{chapter.41}\protected@file@percent }
\newlabel{indexing-guestbooks}{{41}{127}{Indexing Guestbooks}{chapter.41}{}}
\@writefile{toc}{\contentsline {section}{\numberline {41.1}Implementing \texttt {ModelDocumentContributor}}{127}{section.41.1}\protected@file@percent }
\newlabel{implementing-modeldocumentcontributor}{{41.1}{127}{\texorpdfstring {Implementing \texttt {ModelDocumentContributor}}{Implementing ModelDocumentContributor}}{section.41.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {41.2}Implementing \texttt {ModelIndexerWriterContributor}}{128}{section.41.2}\protected@file@percent }
\newlabel{implementing-modelindexerwritercontributor}{{41.2}{128}{\texorpdfstring {Implementing \texttt {ModelIndexerWriterContributor}}{Implementing ModelIndexerWriterContributor}}{section.41.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {42}Querying for Guestbook Documents}{131}{chapter.42}\protected@file@percent }
\newlabel{querying-for-guestbook-documents}{{42}{131}{Querying for Guestbook Documents}{chapter.42}{}}
\@writefile{toc}{\contentsline {section}{\numberline {42.1}Implementing \texttt {KeywordQueryContributor}}{131}{section.42.1}\protected@file@percent }
\newlabel{implementing-keywordquerycontributor}{{42.1}{131}{\texorpdfstring {Implementing \texttt {KeywordQueryContributor}}{Implementing KeywordQueryContributor}}{section.42.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {43}Generating Results Summaries}{133}{chapter.43}\protected@file@percent }
\newlabel{generating-results-summaries}{{43}{133}{Generating Results Summaries}{chapter.43}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {44}Handling Indexing in the Guestbook Service Layer}{135}{chapter.44}\protected@file@percent }
\newlabel{handling-indexing-in-the-guestbook-service-layer}{{44}{135}{Handling Indexing in the Guestbook Service Layer}{chapter.44}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {45}Enabling Search and Indexing for Entries}{137}{chapter.45}\protected@file@percent }
\newlabel{enabling-search-and-indexing-for-entries}{{45}{137}{Enabling Search and Indexing for Entries}{chapter.45}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {46}Registering Entries with the Search Framework}{139}{chapter.46}\protected@file@percent }
\newlabel{registering-entries-with-the-search-framework}{{46}{139}{Registering Entries with the Search Framework}{chapter.46}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {47}Indexing Entries}{141}{chapter.47}\protected@file@percent }
\newlabel{indexing-entries}{{47}{141}{Indexing Entries}{chapter.47}{}}
\@writefile{toc}{\contentsline {section}{\numberline {47.1}Implementing \texttt {ModelDocumentContributor}}{141}{section.47.1}\protected@file@percent }
\newlabel{implementing-modeldocumentcontributor-1}{{47.1}{141}{\texorpdfstring {Implementing \texttt {ModelDocumentContributor}}{Implementing ModelDocumentContributor}}{section.47.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {47.2}Implementing \texttt {ModelIndexerWriterContributor}}{142}{section.47.2}\protected@file@percent }
\newlabel{implementing-modelindexerwritercontributor-1}{{47.2}{142}{\texorpdfstring {Implementing \texttt {ModelIndexerWriterContributor}}{Implementing ModelIndexerWriterContributor}}{section.47.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {47.3}Implementing \texttt {GuestbookEntryBatchReindexer}}{143}{section.47.3}\protected@file@percent }
\newlabel{implementing-guestbookentrybatchreindexer}{{47.3}{143}{\texorpdfstring {Implementing \texttt {GuestbookEntryBatchReindexer}}{Implementing GuestbookEntryBatchReindexer}}{section.47.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {48}Querying for Guestbook Entry Documents}{145}{chapter.48}\protected@file@percent }
\newlabel{querying-for-guestbook-entry-documents}{{48}{145}{Querying for Guestbook Entry Documents}{chapter.48}{}}
\@writefile{toc}{\contentsline {section}{\numberline {48.1}Implementing \texttt {KeywordQueryContributor}}{145}{section.48.1}\protected@file@percent }
\newlabel{implementing-keywordquerycontributor-1}{{48.1}{145}{\texorpdfstring {Implementing \texttt {KeywordQueryContributor}}{Implementing KeywordQueryContributor}}{section.48.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {49}Generating Results Summaries}{147}{chapter.49}\protected@file@percent }
\newlabel{generating-results-summaries-1}{{49}{147}{Generating Results Summaries}{chapter.49}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {50}Handling Indexing in the Entry Service Layer}{149}{chapter.50}\protected@file@percent }
\newlabel{handling-indexing-in-the-entry-service-layer}{{50}{149}{Handling Indexing in the Entry Service Layer}{chapter.50}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {51}Updating Your User Interface For Search}{151}{chapter.51}\protected@file@percent }
\newlabel{updating-your-user-interface-for-search}{{51}{151}{Updating Your User Interface For Search}{chapter.51}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {52}Adding a Search Bar to the Guestbook Portlet}{153}{chapter.52}\protected@file@percent }
\newlabel{adding-a-search-bar-to-the-guestbook-portlet}{{52}{153}{Adding a Search Bar to the Guestbook Portlet}{chapter.52}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {53}Creating a Search Results JSP for the Guestbook Portlet}{155}{chapter.53}\protected@file@percent }
\newlabel{creating-a-search-results-jsp-for-the-guestbook-portlet}{{53}{155}{Creating a Search Results JSP for the Guestbook Portlet}{chapter.53}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {53.1}{\ignorespaces The search results should appear in a search container, and the Actions button should appear for each entry. The search bar should also be displayed.}}{155}{figure.53.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {53.2}{\ignorespaces The Guestbook Application now supports searching for indexed Guestbook Entries.}}{159}{figure.53.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {54}Assets: Integrating with Liferay's Framework}{161}{chapter.54}\protected@file@percent }
\newlabel{assets-integrating-with-liferays-framework}{{54}{161}{Assets: Integrating with Liferay's Framework}{chapter.54}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {55}Enabling Assets at the Service Layer}{163}{chapter.55}\protected@file@percent }
\newlabel{enabling-assets-at-the-service-layer}{{55}{163}{Enabling Assets at the Service Layer}{chapter.55}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {56}Handling Assets for the Guestbook Service}{165}{chapter.56}\protected@file@percent }
\newlabel{handling-assets-for-the-guestbook-service}{{56}{165}{Handling Assets for the Guestbook Service}{chapter.56}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {57}Handling Assets for the GuestbookEntry Service}{167}{chapter.57}\protected@file@percent }
\newlabel{handling-assets-for-the-guestbookentry-service}{{57}{167}{Handling Assets for the GuestbookEntry Service}{chapter.57}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {58}Implementing Asset Renderers}{169}{chapter.58}\protected@file@percent }
\newlabel{implementing-asset-renderers}{{58}{169}{Implementing Asset Renderers}{chapter.58}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {59}Implementing a Guestbook Asset Renderer}{171}{chapter.59}\protected@file@percent }
\newlabel{implementing-a-guestbook-asset-renderer}{{59}{171}{Implementing a Guestbook Asset Renderer}{chapter.59}{}}
\@writefile{toc}{\contentsline {section}{\numberline {59.1}Creating the AssetRenderer Class}{171}{section.59.1}\protected@file@percent }
\newlabel{creating-the-assetrenderer-class}{{59.1}{171}{Creating the AssetRenderer Class}{section.59.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {59.2}Creating the GuestbookAssetRendererFactory Class}{175}{section.59.2}\protected@file@percent }
\newlabel{creating-the-guestbookassetrendererfactory-class}{{59.2}{175}{Creating the GuestbookAssetRendererFactory Class}{section.59.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {60}Implementing a Guestbook Entry Asset Renderer}{179}{chapter.60}\protected@file@percent }
\newlabel{implementing-a-guestbook-entry-asset-renderer}{{60}{179}{Implementing a Guestbook Entry Asset Renderer}{chapter.60}{}}
\@writefile{toc}{\contentsline {section}{\numberline {60.1}Creating the GuestbookEntryAssetRenderer Class}{179}{section.60.1}\protected@file@percent }
\newlabel{creating-the-guestbookentryassetrenderer-class}{{60.1}{179}{Creating the GuestbookEntryAssetRenderer Class}{section.60.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {60.2}Creating the GuestbookEntryAssetRendererFactory Class}{182}{section.60.2}\protected@file@percent }
\newlabel{creating-the-guestbookentryassetrendererfactory-class}{{60.2}{182}{Creating the GuestbookEntryAssetRendererFactory Class}{section.60.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {60.1}{\ignorespaces After you've implemented and registered your asset renderers for your custom entities, the Asset Publisher can display your entities.}}{185}{figure.60.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {61}Adding Asset Features to Your User Interface}{187}{chapter.61}\protected@file@percent }
\newlabel{adding-asset-features-to-your-user-interface}{{61}{187}{Adding Asset Features to Your User Interface}{chapter.61}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {62}Creating JSPs for Displaying Custom Assets in the Asset Publisher}{189}{chapter.62}\protected@file@percent }
\newlabel{creating-jsps-for-displaying-custom-assets-in-the-asset-publisher}{{62}{189}{Creating JSPs for Displaying Custom Assets in the Asset Publisher}{chapter.62}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {62.1}{\ignorespaces When you click the title for a guestbook or guestbook entry in the Asset Publisher, your \texttt {full\_content.jsp} should be displayed.}}{190}{figure.62.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {63}Enabling Tags, Categories, and Related Assets for Guestbooks}{191}{chapter.63}\protected@file@percent }
\newlabel{enabling-tags-categories-and-related-assets-for-guestbooks}{{63}{191}{Enabling Tags, Categories, and Related Assets for Guestbooks}{chapter.63}{}}
\@writefile{toc}{\contentsline {section}{\numberline {63.1}Enabling Asset Features}{191}{section.63.1}\protected@file@percent }
\newlabel{enabling-asset-features}{{63.1}{191}{Enabling Asset Features}{section.63.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {63.1}{\ignorespaces Once you've updated your Guestbook Admin portlet's \texttt {edit\_guestbook.jsp} page, you'll see forms for adding tags and selecting related assets.}}{193}{figure.63.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {64}Enabling Tags, Categories, and Related Assets for Guestbook Entries}{195}{chapter.64}\protected@file@percent }
\newlabel{enabling-tags-categories-and-related-assets-for-guestbook-entries}{{64}{195}{Enabling Tags, Categories, and Related Assets for Guestbook Entries}{chapter.64}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {64.1}{\ignorespaces Now you can see comments, rating, and the full range of asset features.}}{199}{figure.64.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {65}Using Workflow}{201}{chapter.65}\protected@file@percent }
\newlabel{using-workflow}{{65}{201}{Using Workflow}{chapter.65}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {65.1}{\ignorespaces Enable workflow in your assets, just like Liferay DXP's own assets.}}{201}{figure.65.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {66}Supporting Workflow at the Service Layer}{203}{chapter.66}\protected@file@percent }
\newlabel{supporting-workflow-at-the-service-layer}{{66}{203}{Supporting Workflow at the Service Layer}{chapter.66}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {67}Setting the Guestbook Status}{205}{chapter.67}\protected@file@percent }
\newlabel{setting-the-guestbook-status}{{67}{205}{Setting the Guestbook Status}{chapter.67}{}}
\@writefile{toc}{\contentsline {section}{\numberline {67.1}Creating the updateStatus Method}{206}{section.67.1}\protected@file@percent }
\newlabel{creating-the-updatestatus-method}{{67.1}{206}{Creating the updateStatus Method}{section.67.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {68}Setting the Entry Workflow Status}{209}{chapter.68}\protected@file@percent }
\newlabel{setting-the-entry-workflow-status}{{68}{209}{Setting the Entry Workflow Status}{chapter.68}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {69}Retrieving Guestbooks and Entries by Status}{211}{chapter.69}\protected@file@percent }
\newlabel{retrieving-guestbooks-and-entries-by-status}{{69}{211}{Retrieving Guestbooks and Entries by Status}{chapter.69}{}}
\@writefile{toc}{\contentsline {section}{\numberline {69.1}Calling the Persistence Layer}{212}{section.69.1}\protected@file@percent }
\newlabel{calling-the-persistence-layer}{{69.1}{212}{Calling the Persistence Layer}{section.69.1}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {70}Handling Workflow}{213}{chapter.70}\protected@file@percent }
\newlabel{handling-workflow}{{70}{213}{Handling Workflow}{chapter.70}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {71}Creating a Workflow Handler for Guestbooks}{215}{chapter.71}\protected@file@percent }
\newlabel{creating-a-workflow-handler-for-guestbooks}{{71}{215}{Creating a Workflow Handler for Guestbooks}{chapter.71}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {71.1}{\ignorespaces Click the workflow notification in the Notifications portlet to review the guestbook submitted to the workflow.}}{217}{figure.71.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {71.2}{\ignorespaces Click the workflow notification in the Notifications portlet to review the guestbook submitted to the workflow.}}{217}{figure.71.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {72}Creating a Workflow Handler for Guestbook Entries}{219}{chapter.72}\protected@file@percent }
\newlabel{creating-a-workflow-handler-for-guestbook-entries}{{72}{219}{Creating a Workflow Handler for Guestbook Entries}{chapter.72}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {73}Displaying Approved Workflow Items}{221}{chapter.73}\protected@file@percent }
\newlabel{displaying-approved-workflow-items}{{73}{221}{Displaying Approved Workflow Items}{chapter.73}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {74}Displaying Guestbook Status}{223}{chapter.74}\protected@file@percent }
\newlabel{displaying-guestbook-status}{{74}{223}{Displaying Guestbook Status}{chapter.74}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {74.1}{\ignorespaces The Guestbook Admin's main view currently shows the name of the guestbook and its actions button.}}{223}{figure.74.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {74.2}{\ignorespaces The Guestbook Admin's main view, displaying the status of each guestbook.}}{224}{figure.74.2}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {75}Displaying Approved Entries}{225}{chapter.75}\protected@file@percent }
\newlabel{displaying-approved-entries}{{75}{225}{Displaying Approved Entries}{chapter.75}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {75.1}{\ignorespaces If you don't update the counter method to account for workflow status, it displays an incorrect count in the search container.}}{226}{figure.75.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {76}Upgrading Code to 7.0}{227}{chapter.76}\protected@file@percent }
\newlabel{upgrading-code-to-7.0}{{76}{227}{Upgrading Code to 7.0}{chapter.76}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {77}Upgrading Your Development Environment}{231}{chapter.77}\protected@file@percent }
\newlabel{upgrading-your-development-environment}{{77}{231}{Upgrading Your Development Environment}{chapter.77}{}}
\@writefile{toc}{\contentsline {section}{\numberline {77.1}Setting Up Liferay Workspace}{231}{section.77.1}\protected@file@percent }
\newlabel{setting-up-liferay-workspace}{{77.1}{231}{Setting Up Liferay Workspace}{section.77.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {77.2}Creating New Liferay Workspace}{231}{section.77.2}\protected@file@percent }
\newlabel{creating-new-liferay-workspace}{{77.2}{231}{Creating New Liferay Workspace}{section.77.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {77.3}Importing Existing Liferay Workspace}{232}{section.77.3}\protected@file@percent }
\newlabel{importing-existing-liferay-workspace}{{77.3}{232}{Importing Existing Liferay Workspace}{section.77.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {77.4}Configuring Liferay Workspace Settings}{232}{section.77.4}\protected@file@percent }
\newlabel{configuring-liferay-workspace-settings}{{77.4}{232}{Configuring Liferay Workspace Settings}{section.77.4}{}}
\@writefile{toc}{\contentsline {section}{\numberline {77.5}Configure Workspace Product Key}{232}{section.77.5}\protected@file@percent }
\newlabel{configure-workspace-product-key}{{77.5}{232}{Configure Workspace Product Key}{section.77.5}{}}
\@writefile{toc}{\contentsline {section}{\numberline {77.6}Initializing Server Bundle}{232}{section.77.6}\protected@file@percent }
\newlabel{initializing-server-bundle}{{77.6}{232}{Initializing Server Bundle}{section.77.6}{}}
\@writefile{toc}{\contentsline {section}{\numberline {77.7}Migrate .cfg Files to .config Files}{232}{section.77.7}\protected@file@percent }
\newlabel{migrate-.cfg-files-to-.config-files}{{77.7}{232}{Migrate .cfg Files to .config Files}{section.77.7}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {78}Migrating Plugins SDK Projects to Liferay Workspace}{233}{chapter.78}\protected@file@percent }
\newlabel{migrating-plugins-sdk-projects-to-liferay-workspace}{{78}{233}{Migrating Plugins SDK Projects to Liferay Workspace}{chapter.78}{}}
\@writefile{toc}{\contentsline {section}{\numberline {78.1}Importing Existing Plugins SDK Projects}{233}{section.78.1}\protected@file@percent }
\newlabel{importing-existing-plugins-sdk-projects}{{78.1}{233}{Importing Existing Plugins SDK Projects}{section.78.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {78.2}Migrating Existing Plugins to Workspace}{233}{section.78.2}\protected@file@percent }
\newlabel{migrating-existing-plugins-to-workspace}{{78.2}{233}{Migrating Existing Plugins to Workspace}{section.78.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {79}Upgrading Build Dependencies}{235}{chapter.79}\protected@file@percent }
\newlabel{upgrading-build-dependencies}{{79}{235}{Upgrading Build Dependencies}{chapter.79}{}}
\@writefile{toc}{\contentsline {section}{\numberline {79.1}Updating the Repository URL}{235}{section.79.1}\protected@file@percent }
\newlabel{updating-the-repository-url}{{79.1}{235}{Updating the Repository URL}{section.79.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {79.2}Updating the Workspace Plugin Version}{236}{section.79.2}\protected@file@percent }
\newlabel{updating-the-workspace-plugin-version}{{79.2}{236}{Updating the Workspace Plugin Version}{section.79.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {79.3}Removing Your Project's Build Dependency Versions}{236}{section.79.3}\protected@file@percent }
\newlabel{removing-your-projects-build-dependency-versions}{{79.3}{236}{Removing Your Project's Build Dependency Versions}{section.79.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {80}Fixing Upgrade Problems}{237}{chapter.80}\protected@file@percent }
\newlabel{fixing-upgrade-problems}{{80}{237}{Fixing Upgrade Problems}{chapter.80}{}}
\@writefile{toc}{\contentsline {section}{\numberline {80.1}Auto-Correcting Upgrade Problems}{237}{section.80.1}\protected@file@percent }
\newlabel{auto-correcting-upgrade-problems}{{80.1}{237}{Auto-Correcting Upgrade Problems}{section.80.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {80.2}Finding Upgrade Problems}{237}{section.80.2}\protected@file@percent }
\newlabel{finding-upgrade-problems}{{80.2}{237}{Finding Upgrade Problems}{section.80.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {80.3}Resolving Upgrade Problems}{238}{section.80.3}\protected@file@percent }
\newlabel{resolving-upgrade-problems}{{80.3}{238}{Resolving Upgrade Problems}{section.80.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {80.4}Removing Problem Markers}{238}{section.80.4}\protected@file@percent }
\newlabel{removing-problem-markers}{{80.4}{238}{Removing Problem Markers}{section.80.4}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {81}Resolving a Project's Dependencies}{239}{chapter.81}\protected@file@percent }
\newlabel{resolving-a-projects-dependencies}{{81}{239}{Resolving a Project's Dependencies}{chapter.81}{}}
\@writefile{toc}{\contentsline {section}{\numberline {81.1}Class Moved to a Package in the Classpath}{239}{section.81.1}\protected@file@percent }
\newlabel{class-moved-to-a-package-in-the-classpath}{{81.1}{239}{Class Moved to a Package in the Classpath}{section.81.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {81.2}Class Moved to a Module Not in the Classpath}{240}{section.81.2}\protected@file@percent }
\newlabel{class-moved-to-a-module-not-in-the-classpath}{{81.2}{240}{Class Moved to a Module Not in the Classpath}{section.81.2}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {81.1}{\ignorespaces Liferay refactored the portal-service JAR for 7.0. Application APIs now exist in their own modules, and the portal-service JAR is now \emph {portal-kernel}.}}{240}{figure.81.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {81.3}Class Replaced or Removed}{241}{section.81.3}\protected@file@percent }
\newlabel{class-replaced-or-removed}{{81.3}{241}{Class Replaced or Removed}{section.81.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {82}Resolving Breaking Changes}{243}{chapter.82}\protected@file@percent }
\newlabel{resolving-breaking-changes}{{82}{243}{Resolving Breaking Changes}{chapter.82}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {83}Upgrading Service Builder Services}{245}{chapter.83}\protected@file@percent }
\newlabel{upgrading-service-builder-services}{{83}{245}{Upgrading Service Builder Services}{chapter.83}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {84}Removing Legacy Files}{247}{chapter.84}\protected@file@percent }
\newlabel{removing-legacy-files}{{84}{247}{Removing Legacy Files}{chapter.84}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {85}Converting a Service Builder Module from Spring DI to OSGi DS}{249}{chapter.85}\protected@file@percent }
\newlabel{converting-a-service-builder-module-from-spring-di-to-osgi-ds}{{85}{249}{Converting a Service Builder Module from Spring DI to OSGi DS}{chapter.85}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {86}Rebuilding Services}{251}{chapter.86}\protected@file@percent }
\newlabel{rebuilding-services}{{86}{251}{Rebuilding Services}{chapter.86}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {87}Upgrading Customization Plugins}{253}{chapter.87}\protected@file@percent }
\newlabel{upgrading-customization-plugins}{{87}{253}{Upgrading Customization Plugins}{chapter.87}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {88}Upgrading Customization Modules}{255}{chapter.88}\protected@file@percent }
\newlabel{upgrading-customization-modules}{{88}{255}{Upgrading Customization Modules}{chapter.88}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {89}Upgrading Core JSP Hooks}{257}{chapter.89}\protected@file@percent }
\newlabel{upgrading-core-jsp-hooks}{{89}{257}{Upgrading Core JSP Hooks}{chapter.89}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {90}Upgrading Portlet JSP Hooks}{259}{chapter.90}\protected@file@percent }
\newlabel{upgrading-portlet-jsp-hooks}{{90}{259}{Upgrading Portlet JSP Hooks}{chapter.90}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {91}Upgrading Service Wrapper Hooks}{261}{chapter.91}\protected@file@percent }
\newlabel{upgrading-service-wrapper-hooks}{{91}{261}{Upgrading Service Wrapper Hooks}{chapter.91}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {92}Upgrading Core Language Key Hooks}{263}{chapter.92}\protected@file@percent }
\newlabel{upgrading-core-language-key-hooks}{{92}{263}{Upgrading Core Language Key Hooks}{chapter.92}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {93}Upgrading Portlet Language Key Hooks}{265}{chapter.93}\protected@file@percent }
\newlabel{upgrading-portlet-language-key-hooks}{{93}{265}{Upgrading Portlet Language Key Hooks}{chapter.93}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {94}Upgrading Model Listener Hooks}{267}{chapter.94}\protected@file@percent }
\newlabel{upgrading-model-listener-hooks}{{94}{267}{Upgrading Model Listener Hooks}{chapter.94}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {95}Upgrading Event Action Hooks}{269}{chapter.95}\protected@file@percent }
\newlabel{upgrading-event-action-hooks}{{95}{269}{Upgrading Event Action Hooks}{chapter.95}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {96}Upgrading Servlet Filter Hooks}{271}{chapter.96}\protected@file@percent }
\newlabel{upgrading-servlet-filter-hooks}{{96}{271}{Upgrading Servlet Filter Hooks}{chapter.96}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {97}Upgrading Portal Property Hooks}{273}{chapter.97}\protected@file@percent }
\newlabel{upgrading-portal-property-hooks}{{97}{273}{Upgrading Portal Property Hooks}{chapter.97}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {98}Upgrading Struts Action Hooks}{275}{chapter.98}\protected@file@percent }
\newlabel{upgrading-struts-action-hooks}{{98}{275}{Upgrading Struts Action Hooks}{chapter.98}{}}
\@writefile{toc}{\contentsline {section}{\numberline {98.1}Converting Your Old Wrapper to \texttt {MVCCommand}s}{275}{section.98.1}\protected@file@percent }
\newlabel{converting-your-old-wrapper-to-mvccommands}{{98.1}{275}{\texorpdfstring {Converting Your Old Wrapper to \texttt {MVCCommand}s}{Converting Your Old Wrapper to MVCCommands}}{section.98.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {98.2}Mapping Your \texttt {MVCCommand} URLs}{276}{section.98.2}\protected@file@percent }
\newlabel{mapping-your-mvccommand-urls}{{98.2}{276}{\texorpdfstring {Mapping Your \texttt {MVCCommand} URLs}{Mapping Your MVCCommand URLs}}{section.98.2}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {99}Upgrading a Theme to 7.2}{277}{chapter.99}\protected@file@percent }
\newlabel{upgrading-a-theme-to-7.2}{{99}{277}{Upgrading a Theme to 7.2}{chapter.99}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {100}Upgrading Your Theme from Liferay Portal 6.2 to 7.2}{279}{chapter.100}\protected@file@percent }
\newlabel{upgrading-your-theme-from-liferay-portal-6.2-to-7.2}{{100}{279}{Upgrading Your Theme from Liferay Portal 6.2 to 7.2}{chapter.100}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {100.1}{\ignorespaces The Lunar Resort example theme upgraded in this tutorial uses a clean, minimal design.}}{280}{figure.100.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {101}Setting up the Development Environment}{281}{chapter.101}\protected@file@percent }
\newlabel{setting-up-the-development-environment}{{101}{281}{Setting up the Development Environment}{chapter.101}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {102}Installing the Liferay Theme Generator to Import a 6.2 Theme}{283}{chapter.102}\protected@file@percent }
\newlabel{installing-the-liferay-theme-generator-to-import-a-6.2-theme}{{102}{283}{Installing the Liferay Theme Generator to Import a 6.2 Theme}{chapter.102}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {103}Importing the Theme into the Liferay JS Theme Toolkit}{285}{chapter.103}\protected@file@percent }
\newlabel{importing-the-theme-into-the-liferay-js-theme-toolkit}{{103}{285}{Importing the Theme into the Liferay JS Theme Toolkit}{chapter.103}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {104}Running the Upgrade Task for 6.2 Themes}{287}{chapter.104}\protected@file@percent }
\newlabel{running-the-upgrade-task-for-6.2-themes}{{104}{287}{Running the Upgrade Task for 6.2 Themes}{chapter.104}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {105}Updating 6.2 CSS Code}{289}{chapter.105}\protected@file@percent }
\newlabel{updating-6.2-css-code}{{105}{289}{Updating 6.2 CSS Code}{chapter.105}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {106}Updating 6.2 CSS Rules and Imports}{291}{chapter.106}\protected@file@percent }
\newlabel{updating-6.2-css-rules-and-imports}{{106}{291}{Updating 6.2 CSS Rules and Imports}{chapter.106}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {107}Updating the Responsiveness}{295}{chapter.107}\protected@file@percent }
\newlabel{updating-the-responsiveness}{{107}{295}{Updating the Responsiveness}{chapter.107}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {108}Updating 6.2 Theme Templates}{297}{chapter.108}\protected@file@percent }
\newlabel{updating-6.2-theme-templates}{{108}{297}{Updating 6.2 Theme Templates}{chapter.108}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {108.1}{\ignorespaces The Dockbar was removed and must be replaced with the new Control Menu.}}{298}{figure.108.1}\protected@file@percent }
\gdef \LT@i {\LT@entry
{1}{107.11876pt}\LT@entry
{1}{362.63625pt}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {109}Updating 6.2 Portal Normal Theme Template}{299}{chapter.109}\protected@file@percent }
\newlabel{updating-6.2-portal-normal-theme-template}{{109}{299}{Updating 6.2 Portal Normal Theme Template}{chapter.109}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {110}Updating 6.2 Navigation Theme Template}{301}{chapter.110}\protected@file@percent }
\newlabel{updating-6.2-navigation-theme-template}{{110}{301}{Updating 6.2 Navigation Theme Template}{chapter.110}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {111}Updating 6.2 Init Custom Theme Template}{303}{chapter.111}\protected@file@percent }
\newlabel{updating-6.2-init-custom-theme-template}{{111}{303}{Updating 6.2 Init Custom Theme Template}{chapter.111}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {112}Updating the Resources Importer}{305}{chapter.112}\protected@file@percent }
\newlabel{updating-the-resources-importer}{{112}{305}{Updating the Resources Importer}{chapter.112}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {113}Updating 6.2 Liferay Plugin Package Properties}{307}{chapter.113}\protected@file@percent }
\newlabel{updating-6.2-liferay-plugin-package-properties}{{113}{307}{Updating 6.2 Liferay Plugin Package Properties}{chapter.113}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {114}Updating 6.2 Web Content}{309}{chapter.114}\protected@file@percent }
\newlabel{updating-6.2-web-content}{{114}{309}{Updating 6.2 Web Content}{chapter.114}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {115}Updating the 6.2 Sitemap}{313}{chapter.115}\protected@file@percent }
\newlabel{updating-the-6.2-sitemap}{{115}{313}{Updating the 6.2 Sitemap}{chapter.115}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {116}Applying Clay Design Patterns}{315}{chapter.116}\protected@file@percent }
\newlabel{applying-clay-design-patterns}{{116}{315}{Applying Clay Design Patterns}{chapter.116}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {117}Upgrading Your Theme from Liferay Portal 7.0 to 7.2}{317}{chapter.117}\protected@file@percent }
\newlabel{upgrading-your-theme-from-liferay-portal-7.0-to-7.2}{{117}{317}{Upgrading Your Theme from Liferay Portal 7.0 to 7.2}{chapter.117}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {118}Running the Upgrade Task for 7.0 Themes}{319}{chapter.118}\protected@file@percent }
\newlabel{running-the-upgrade-task-for-7.0-themes}{{118}{319}{Running the Upgrade Task for 7.0 Themes}{chapter.118}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {119}Updating 7.0 CSS Code}{321}{chapter.119}\protected@file@percent }
\newlabel{updating-7.0-css-code}{{119}{321}{Updating 7.0 CSS Code}{chapter.119}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {120}Updating 7.0 CSS File Names for Clay}{323}{chapter.120}\protected@file@percent }
\newlabel{updating-7.0-css-file-names-for-clay}{{120}{323}{Updating 7.0 CSS File Names for Clay}{chapter.120}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {121}Updating 7.0 Class Variables}{325}{chapter.121}\protected@file@percent }
\newlabel{updating-7.0-class-variables}{{121}{325}{Updating 7.0 Class Variables}{chapter.121}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {122}Updating 7.0 Imports}{327}{chapter.122}\protected@file@percent }
\newlabel{updating-7.0-imports}{{122}{327}{Updating 7.0 Imports}{chapter.122}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {123}Updating 7.0 Theme Templates to 7.2}{329}{chapter.123}\protected@file@percent }
\newlabel{updating-7.0-theme-templates-to-7.2}{{123}{329}{Updating 7.0 Theme Templates to 7.2}{chapter.123}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {124}Updating 7.0 Theme Templates}{331}{chapter.124}\protected@file@percent }
\newlabel{updating-7.0-theme-templates}{{124}{331}{Updating 7.0 Theme Templates}{chapter.124}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {125}Using the Bootstrap 3 Lexicon CSS Compatibility Layer}{333}{chapter.125}\protected@file@percent }
\newlabel{using-the-bootstrap-3-lexicon-css-compatibility-layer}{{125}{333}{Using the Bootstrap 3 Lexicon CSS Compatibility Layer}{chapter.125}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {126}Upgrading 7.1 Themes to 7.2}{335}{chapter.126}\protected@file@percent }
\newlabel{upgrading-7.1-themes-to-7.2}{{126}{335}{Upgrading 7.1 Themes to 7.2}{chapter.126}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {127}Upgrading a Layout Template to 7.2}{337}{chapter.127}\protected@file@percent }
\newlabel{upgrading-a-layout-template-to-7.2}{{127}{337}{Upgrading a Layout Template to 7.2}{chapter.127}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {128}Upgrading 6.2 Layout Templates to 7.2}{339}{chapter.128}\protected@file@percent }
\newlabel{upgrading-6.2-layout-templates-to-7.2}{{128}{339}{Upgrading 6.2 Layout Templates to 7.2}{chapter.128}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {129}Upgrading 7.0 and 7.1 Layout Templates to 7.2}{341}{chapter.129}\protected@file@percent }
\newlabel{upgrading-7.0-and-7.1-layout-templates-to-7.2}{{129}{341}{Upgrading 7.0 and 7.1 Layout Templates to 7.2}{chapter.129}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {130}Upgrading Frameworks and Features}{343}{chapter.130}\protected@file@percent }
\newlabel{upgrading-frameworks-and-features}{{130}{343}{Upgrading Frameworks and Features}{chapter.130}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {131}Upgrading JNDI Data Source Usage}{345}{chapter.131}\protected@file@percent }
\newlabel{upgrading-jndi-data-source-usage}{{131}{345}{Upgrading JNDI Data Source Usage}{chapter.131}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {132}Upgrading Service Builder Service Invocation}{347}{chapter.132}\protected@file@percent }
\newlabel{upgrading-service-builder-service-invocation}{{132}{347}{Upgrading Service Builder Service Invocation}{chapter.132}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {133}Upgrading Service Builder}{349}{chapter.133}\protected@file@percent }
\newlabel{upgrading-service-builder}{{133}{349}{Upgrading Service Builder}{chapter.133}{}}
\@writefile{toc}{\contentsline {section}{\numberline {133.1}Step 1: Adapt the Code to 7.0's API}{349}{section.133.1}\protected@file@percent }
\newlabel{step-1-adapt-the-code-to-7.0s-api}{{133.1}{349}{Step 1: Adapt the Code to 7.0's API}{section.133.1}{}}
\@writefile{toc}{\contentsline {section}{\numberline {133.2}Step 2: Resolve Dependencies}{350}{section.133.2}\protected@file@percent }
\newlabel{step-2-resolve-dependencies}{{133.2}{350}{Step 2: Resolve Dependencies}{section.133.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {133.3}Step 3: Build the Services}{350}{section.133.3}\protected@file@percent }
\newlabel{step-3-build-the-services}{{133.3}{350}{Step 3: Build the Services}{section.133.3}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {134}Migrating Off of Velocity Templates}{351}{chapter.134}\protected@file@percent }
\newlabel{migrating-off-of-velocity-templates}{{134}{351}{Migrating Off of Velocity Templates}{chapter.134}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {135}Upgrading Portlets}{353}{chapter.135}\protected@file@percent }
\newlabel{upgrading-portlets}{{135}{353}{Upgrading Portlets}{chapter.135}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {136}Upgrading a GenericPortlet}{355}{chapter.136}\protected@file@percent }
\newlabel{upgrading-a-genericportlet}{{136}{355}{Upgrading a GenericPortlet}{chapter.136}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {137}Upgrading a Liferay MVC Portlet}{357}{chapter.137}\protected@file@percent }
\newlabel{upgrading-a-liferay-mvc-portlet}{{137}{357}{Upgrading a Liferay MVC Portlet}{chapter.137}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {138}Upgrading a Liferay JSF Portlet}{359}{chapter.138}\protected@file@percent }
\newlabel{upgrading-a-liferay-jsf-portlet}{{138}{359}{Upgrading a Liferay JSF Portlet}{chapter.138}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {138.1}{\ignorespaces The Liferay Faces site gives you options to generate dependencies for many environments.}}{360}{figure.138.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {139}Upgrading a Servlet-based Portlet}{363}{chapter.139}\protected@file@percent }
\newlabel{upgrading-a-servlet-based-portlet}{{139}{363}{Upgrading a Servlet-based Portlet}{chapter.139}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {140}Upgrading a Spring Portlet MVC Portlet}{365}{chapter.140}\protected@file@percent }
\newlabel{upgrading-a-spring-portlet-mvc-portlet}{{140}{365}{Upgrading a Spring Portlet MVC Portlet}{chapter.140}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {141}Upgrading a Struts 1 Portlet}{367}{chapter.141}\protected@file@percent }
\newlabel{upgrading-a-struts-1-portlet}{{141}{367}{Upgrading a Struts 1 Portlet}{chapter.141}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {142}Upgrading Web Plugins}{369}{chapter.142}\protected@file@percent }
\newlabel{upgrading-web-plugins}{{142}{369}{Upgrading Web Plugins}{chapter.142}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {143}Upgrading Ext Plugins}{371}{chapter.143}\protected@file@percent }
\newlabel{upgrading-ext-plugins}{{143}{371}{Upgrading Ext Plugins}{chapter.143}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {144}Creating a Theme}{373}{chapter.144}\protected@file@percent }
\newlabel{creating-a-theme}{{144}{373}{Creating a Theme}{chapter.144}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {144.1}{\ignorespaces The finished Lunar Resort Theme uses Liferay DXP's tools to produce a user-friendly UI that is maintainable.}}{374}{figure.144.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {145}Setting up the Theme}{375}{chapter.145}\protected@file@percent }
\newlabel{setting-up-the-theme}{{145}{375}{Setting up the Theme}{chapter.145}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {145.1}{\ignorespaces Answer no for the Font Awesome Prompt}}{376}{figure.145.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {146}Customizing the Lunar Resort's Header and Logo}{377}{chapter.146}\protected@file@percent }
\newlabel{customizing-the-lunar-resorts-header-and-logo}{{146}{377}{Customizing the Lunar Resort's Header and Logo}{chapter.146}{}}
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {147}Customizing the Navigation}{381}{chapter.147}\protected@file@percent }
\newlabel{customizing-the-navigation}{{147}{381}{Customizing the Navigation}{chapter.147}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {147.1}{\ignorespaces The updated Header and navigation are much more user-friendly now.}}{386}{figure.147.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {148}Defining the Lunar Resort's Footer and Footer Navigation}{387}{chapter.148}\protected@file@percent }
\newlabel{defining-the-lunar-resorts-footer-and-footer-navigation}{{148}{387}{Defining the Lunar Resort's Footer and Footer Navigation}{chapter.148}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {148.1}{\ignorespaces The updated Footer provides everything visitors need to follow and contact the Lunar Resort.}}{390}{figure.148.1}\protected@file@percent }
\@writefile{lof}{\addvspace {10pt}}
\@writefile{lot}{\addvspace {10pt}}
\@writefile{toc}{\contentsline {chapter}{\chapternumberline {149}Adding a Color Scheme Variant for the Lunar Resort Theme}{391}{chapter.149}\protected@file@percent }
\newlabel{adding-a-color-scheme-variant-for-the-lunar-resort-theme}{{149}{391}{Adding a Color Scheme Variant for the Lunar Resort Theme}{chapter.149}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {149.1}{\ignorespaces Color schemes are a good way to subtly change the look and feel of your site.}}{394}{figure.149.1}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {149.2}{\ignorespaces The finished color scheme gives the Lunar Resort site a fiery glow.}}{395}{figure.149.2}\protected@file@percent }
\@setckpt{developer/tutorials}{
\setcounter{page}{396}
\setcounter{equation}{0}
\setcounter{enumi}{8}
\setcounter{enumii}{6}
\setcounter{enumiii}{3}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{@memmarkcntra}{-1}
\setcounter{storedpagenumber}{1}
\setcounter{book}{0}
\setcounter{part}{1}
\setcounter{chapter}{149}
\setcounter{section}{0}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{@ppsavesec}{0}
\setcounter{@ppsaveapp}{0}
\setcounter{vslineno}{0}
\setcounter{poemline}{0}
\setcounter{modulo@vs}{0}
\setcounter{memfvsline}{0}
\setcounter{verse}{0}
\setcounter{chrsinstr}{0}
\setcounter{poem}{0}
\setcounter{newflo@tctr}{4}
\setcounter{@contsubnum}{0}
\setcounter{section@level}{0}
\setcounter{maxsecnumdepth}{1}
\setcounter{sidefootnote}{0}
\setcounter{pagenote}{0}
\setcounter{pagenoteshadow}{0}
\setcounter{memfbvline}{0}
\setcounter{bvlinectr}{0}
\setcounter{cp@cntr}{0}
\setcounter{ism@mctr}{0}
\setcounter{xsm@mctr}{0}
\setcounter{csm@mctr}{0}
\setcounter{ksm@mctr}{0}
\setcounter{xksm@mctr}{0}
\setcounter{cksm@mctr}{0}
\setcounter{msm@mctr}{0}
\setcounter{xmsm@mctr}{0}
\setcounter{cmsm@mctr}{0}
\setcounter{bsm@mctr}{0}
\setcounter{workm@mctr}{0}
\setcounter{sheetsequence}{468}
\setcounter{lastsheet}{2851}
\setcounter{lastpage}{2779}
\setcounter{figure}{2}
\setcounter{lofdepth}{1}
\setcounter{table}{0}
\setcounter{lotdepth}{1}
\setcounter{Item}{599}
\setcounter{Hfootnote}{5}
\setcounter{bookmark@seq@number}{0}
\setcounter{memhycontfloat}{0}
\setcounter{Hpagenote}{0}
\setcounter{r@tfl@t}{0}
\setcounter{float@type}{4}
\setcounter{LT@tables}{1}
\setcounter{LT@chunks}{3}
\setcounter{parentequation}{0}
\setcounter{FancyVerbLine}{0}
}
================================================
FILE: book/developer/tutorials.tex
================================================
\chapter{Developer Tutorials}\label{developer-tutorials}
Liferay's developer tutorials help you learn from scratch. They are the
``opinionated'' way to work with code on Liferay's platform. Here,
you'll learn these things:
\begin{itemize}
\item
Writing applications on various frameworks, such as Bean Portlet,
PortletMVC4Spring, and Liferay MVC Portlet (coming soon)
\item
Writing front-end applications using Angular, React, and Vue (coming
soon)
\item
Upgrading your code from older versions of Liferay DXP
\item
Creating back-end and headless services (coming soon)
\end{itemize}
\chapter{Developing a Web
Application}\label{developing-a-web-application}
This tutorial shows you how to build a Liferay application from
beginning to end. Starting by designing the back-end using Liferay's
object-relational mapper Service Builder, you'll move from there to
designing the widget that calls all of those services. In the process,
you'll learn about many of Liferay's development frameworks, including
\begin{itemize}
\tightlist
\item
Permissions
\item
Search
\item
Assets
\item
REST Builder web services
\item
Workflow
\item
Staging
\end{itemize}
When you're finished, the application looks like this:
\begin{figure}
\centering
\includegraphics{./images/finished-guestbook-intro.png}
\caption{It looks humble, but there's a lot of functionality packed in
this application.}
\end{figure}
This tutorial assumes nothing, so it starts at the beginning: setting up
a Liferay development environment. Though you can use anything from a
text editor and the command line to your Java IDE of choice, Liferay Dev
Studio DXP optimizes development on Liferay's platform. It integrates
Liferay's Blade tools for modular development.
Once you have an environment ready, you'll jump right in and start
creating your object-relational map. After you've created your back-end,
you'll move to the front-end.
From there you'll see everything from UI standards to providing remote
services. Once everything is completed and wrapped up with a bow, you
can distribute the application on Marketplace.
Let's Go!{}
\chapter{Setting Up a Development
Environment}\label{setting-up-a-development-environment}
Liferay's development tools help you get started fast. All you need as a
prerequisite is a Java Development Kit version 8
(\href{http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html}{JDK}
or \href{https://jdk.java.net/java-se-ri/8}{OpenJDK} is fine). Once
that's installed, there are only three steps.
\begin{itemize}
\item
Download a Liferay Dev Studio DXP bundle.
\item
Unzip the downloaded package to a location on your system.
\item
Start Dev Studio DXP.
\end{itemize}
You'll follow these steps and then generate a Liferay Workspace for
developing your first Liferay DXP application.
\section{Installing a Liferay Dev Studio DXP
Bundle}\label{installing-a-liferay-dev-studio-dxp-bundle}
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Download and install
\href{/docs/7-2/reference/-/knowledge_base/r/installing-liferay-dev-studio}{Liferay
Dev Studio DXP} Installing it is easy: run the installer.
\item
To run Liferay Dev Studio DXP, run the \texttt{DeveloperStudio}
executable. On Windows, the installer doesn't create a menu entry, so
you should add a shortcut to it to your desktop or task bar.
\end{enumerate}
The first time you start Liferay Dev Studio DXP, it prompts you to
select an Eclipse workspace. If you specify an empty folder, Liferay Dev
Studio DXP creates a new workspace in that folder. Follow these steps to
create a new workspace:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
When prompted, indicate your workspace's path. Name your new workspace
\texttt{guestbook-workspace} and click \emph{OK}. Windows has path
length limitations, so on that OS, you may want to create your
workspace the root folder (\texttt{C:\textbackslash{}}).
\item
When Liferay Dev Studio DXP first launches, it presents a welcome
page. Click the \emph{Workbench} icon to continue.
\end{enumerate}
Nice job! Your development environment is installed and your Eclipse
workspace is set up.
\section{Creating a Liferay
Workspace}\label{creating-a-liferay-workspace}
Now you'll create another kind of workspace: a
\href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay
Workspace}. Liferay Workspace helps manage Liferay projects by providing
various build scripts, properties, and configuration for you. Liferay
Workspace uses
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} and
\href{https://gradle.org/}{Gradle} to manage dependencies and organize
your build environment. Note that to avoid configuration issues, you can
only create one Liferay Workspace for each Eclipse Workspace.
Follow these steps to create a Liferay Workspace in Liferay Dev Studio
DXP:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Select \emph{File} → \emph{New} → \emph{Liferay Workspace Project}.
Note: you may have to select \emph{File} → \emph{New} → \emph{Other},
then choose \emph{Liferay Workspace Project} in the \emph{Liferay}
category.
\begin{figure}
\centering
\includegraphics{./images/dev-studio-create-workspace.png}
\caption{By selecting \emph{Liferay Workspace}, you begin the process
of creating a new workspace for your Liferay DXP projects.}
\end{figure}
A \emph{New Liferay Workspace} dialog appears, which presents several
configuration options.
\item
Give your workspace the name \texttt{com-liferay-docs-guestbook}.
\item
Next, choose your workspace's location. Leave the default setting
checked. This places your Liferay Workspace inside your Eclipse
workspace.
\item
For \emph{Liferay Version}, 7.2 should already be selected.
\item
Leave the rest of the defaults.
\item
Check the \emph{Download Liferay bundle} checkbox to download and
unzip a Liferay DXP instance in your workspace automatically. When
prompted, name the server \texttt{liferay-tomcat-bundle}.
\item
Click \emph{Finish} to create your Liferay Workspace. This may take a
while because Liferay Liferay DXP downloads the Liferay DXP bundle in
the background.
\end{enumerate}
Congratulations! Your development environment is ready! Next, you'll get
started developing your first Liferay DXP application.
\chapter{Generating the Back-end}\label{generating-the-back-end}
You can start writing an application in either the front-end or the
back-end. If you start with the front-end, you design the screens and
forms first, using mock data. If you start with the back-end, you create
your data store up front, and then you can create your front-end later.
This is what you'll do with the Guestbook application.
A \emph{persistence} layer and a \emph{service} layer make up the bottom
layers of your back-end. You'll persist guestbooks and their entries to
a database. Your service layer calls your persistence layer, providing a
buffer in case you wish to swap out your persistence technology later.
\begin{figure}
\centering
\includegraphics{./images/application-layers.png}
\caption{Service Builder generates the shaded layers of your
application.}
\end{figure}
\emph{Service Builder} is Liferay's code generation tool for defining
object models and mapping those models to SQL databases. By defining
your model in a single XML file, you can generate your object model (the
M in MVC), your service layer, and your persistence layer all in one
shot. At the same time, you can support every database Liferay DXP
supports.
Ready to begin?
Let's Go!{}
\chapter{What is Service Builder?}\label{what-is-service-builder}
\begin{verbatim}
Generating the Back-End
Step 1 of 3
\end{verbatim}
Now you'll use Service Builder to generate your application's Model,
Service, and Persistence layers. Then you can add your application's
necessary business logic.
\section{Guestbook Application
Design}\label{guestbook-application-design}
The Guestbook application handles multiple Guestbooks and their entries.
To make this work, you'll create two tables in the database: one for
guestbooks and one for guestbook entries.
\begin{figure}
\centering
\includegraphics{./images/guestbook-final.png}
\caption{When you're done, the Guestbook supports multiple guestbooks
and makes use of many Liferay features.}
\end{figure}
\section{Service Layer}\label{service-layer}
This application is data-driven. It uses services for storing and
retrieving data. The application asks for data, and the service fetches
it from the persistence layer. The application can then display this
data to the user, who reads or modifies it. If the data is modified, the
application passes it back to the service, which calls the persistence
layer to store it. The application doesn't need to know anything about
how the service does what it does.
To get started, you'll create a Service Builder project and populate its
\texttt{service.xml} file with all the necessary entities to generate
this code:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In Liferay Dev Studio DXP, click \emph{File} → \emph{New} →
\emph{Liferay Module Project}.
\item
Name the project \texttt{guestbook}.
\item
Select \texttt{service-builder} for the Project Template Name.
\item
Click \emph{Next}.
\item
Enter \texttt{com.liferay.docs.guestbook} for the \emph{Package Name}.
\item
Click \emph{Finish}.
\end{enumerate}
This creates two modules: an API module (\texttt{guestbook-api}) and a
service module (\texttt{guestbook-service}). Next, you'll learn how to
use them.
\begin{figure}
\centering
\includegraphics{./images/guestbook-service-project.png}
\caption{Your current project structure.}
\end{figure}
\chapter{Generating Model, Service, and Persistence
Layers}\label{generating-model-service-and-persistence-layers}
\begin{verbatim}
Generating the Back-End
Step 2 of 3
\end{verbatim}
To model the guestbooks and entries, you'll create guestbook and entry
model classes. But you won't do this directly in Java. Instead, you'll
define them in Service Builder, which generates your object model and
maps it to all the SQL databases Liferay DXP supports.
This application's design allows for multiple guestbooks, each
containing different sets of entries. All users with permission to
access the application can add entries, but only administrative users
can add guestbooks.
It's time to get started. You'll create the \texttt{Guestbook} entity
first:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In your \texttt{guestbook-service} project, open \texttt{service.xml}.
Make sure the \emph{Source} tab is selected.
\item
When Liferay Dev Studio DXP generated your project, it filled this
file with dummy entities, which you'll replace. Remove everything in
the file below the \texttt{DOCTYPE}. Replace the file's opening
contents with the following code:
\begin{verbatim}
liferay
GB
\end{verbatim}
This defines the author, namespace, and the entity name. The namespace
keeps the database field names from conflicting. The last tag is the
opening tag for the \texttt{Guestbook} entity definition. In this tag,
you enable local and remote services for the entity, define its name,
and specify that it should have a
\href{https://en.wikipedia.org/wiki/Universally_unique_identifier}{universally
unique identifier (UUID)}.
\item
The Guestbook requires only two fields: a primary key to identify it
uniquely in the database, and a name. Add these fields:
\begin{verbatim}
\end{verbatim}
This defines \texttt{guestbookId} as the entity's primary key of the
type \texttt{long} and the name as a \texttt{String}.
\item
Next, define the group instance. The \texttt{groupId} defines the ID
of the Site in Liferay DXP where the entity instance exists. The
\texttt{companyId} is the primary key of a
\href{/docs/7-2/user/-/knowledge_base/u/setting-up}{portal instance}.
\begin{verbatim}
\end{verbatim}
\item
Next, add audit fields. These fields help you track owners of entity
instances, along with those instances' create and modify dates:
\begin{verbatim}
\end{verbatim}
\item
After this, add fields that support Liferay's workflow system. These
provide fields in the database to track your entity's status as it
passes through the workflow.
\begin{verbatim}
\end{verbatim}
\item
Before the closing \texttt{\textless{}/entity\textgreater{}} tag, add
this finder definition:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
A
\href{/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods}{finder}
generates a \texttt{get} method for retrieving Guestbook entities. The
fields used by the finder define the scope of the data retrieved. This
finder gets all Guestbooks by their \texttt{groupId}. This is how
administrators put Guestbooks on multiple Sites, and each
\texttt{Guestbook} has its own data scoped to its Site.
The \texttt{Guestbook} entity is finished for now. Next, you'll create
the \texttt{GuestbookEntry} entity:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the opening entity tag:
\begin{verbatim}
\end{verbatim}
As with the \texttt{Guestbook} entity, you enable local and remote
services, define the entity's name, and specify that it should have a
UUID.
\item
Add the fields that define the \texttt{GuestbookEntry}'s data:
\begin{verbatim}
\end{verbatim}
The \texttt{name}, \texttt{email}, and \texttt{message} fields
comprise a \texttt{GuestbookEntry}. These fields define the name of
the person creating the entry, an email address, and the Guestbook
message, respectively. The \texttt{guestbookId} is assigned
automatically by code you'll write, and is a foreign key to the
\texttt{Guestbook} where this entry belongs.
\item
Add fields to track the portal instance and group:
\begin{verbatim}
\end{verbatim}
\item
Add audit fields:
\begin{verbatim}
\end{verbatim}
\item
Add status fields to track workflow:
\begin{verbatim}
\end{verbatim}
\item
When querying for \texttt{GuestbookEntry}s, you can order them by one
or more columns. Since visitors sign \texttt{Guestbook}s in order by
time, order your \texttt{GuestbookEntry} instances by the date they
were created:
\begin{verbatim}
\end{verbatim}
\item
Add a finder that retrieves \texttt{GuestbooEntry}s by a combination
of \texttt{groupId} and \texttt{guestbookId}. This supports Liferay
DXP's multi-tenancy by only returning those entries that belong both
to the current Site and the current Guestbook. After defining your
finder add the closing entity tag:
\begin{verbatim}
\end{verbatim}
\item
Define exception types outside the
\texttt{\textless{}entity\textgreater{}} tags, just before the closing
\texttt{\textless{}/service-builder\textgreater{}} tag:
\begin{verbatim}
GuestbookEntryEmail
GuestbookEntryMessage
GuestbookEntryName
GuestbookName
\end{verbatim}
These generate exception classes you'll use later in try/catch
statements.
\item
Save your \texttt{service.xml} file.
\end{enumerate}
Now you're ready to run Service Builder to generate your model, service,
and persistence layers!
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the Gradle Tasks pane on the right side of Dev Studio DXP, open
\texttt{com-liferay-docs-guestbook} → \texttt{modules} →
\texttt{guestbook} → \texttt{guestbook-service} → \texttt{build}.
\item
Run \texttt{buildService} by right-clicking it and selecting \emph{Run
Gradle Tasks}. Make sure you're connected to the Internet, as Gradle
downloads dependencies the first time you run it.
\item
In the Project Explorer, right-click the \texttt{guestbook-service}
module and select \emph{Refresh}. Repeat this step for the
\texttt{guestbook-api} module. This ensures that the new classes and
interfaces generated by Service Builder show up in Dev Studio DXP.
\item
In the Project Explorer, right-click the \texttt{guestbook-service}
module and select \emph{Gradle} → \emph{Refresh Gradle Project}.
Repeat this step for the \texttt{guestbook-api} module. This ensures
that your modules' Gradle dependencies are up to date.
\end{enumerate}
Service Builder is based on a design philosophy called loose coupling.
It generates three layers of your application: the model, the service,
and the persistence layers. Loose coupling means you can swap out the
persistence layer with little to no change in the model and service
layers. The model is in the \texttt{-api} module, and the service and
persistence layers are in the \texttt{-service} module.
\begin{figure}
\centering
\includegraphics{./images/model-service-persistence.png}
\caption{The Model, Service, and Persistence Layer comprise a loose
coupling design.}
\end{figure}
Each layer is implemented using Java Interfaces and implementations of
those interfaces. Rather than have one \texttt{Guestbook} class that
represents your model, Service Builder generates a system of classes
that includes a \texttt{Guestbook} interface, a
\texttt{GuestbookBaseImpl} abstract class that Service Builder manages,
and a \texttt{GuestbookImpl} class that you can customize. With this
design, you can customize your model and let Service Builder generate
the tedious-to-write code. That's why Service Builder is a code
generator for code generator haters.
Next, you'll create the service implementations.
\chapter{Implementing Service
Methods}\label{implementing-service-methods}
\begin{verbatim}
Generating the Back-End
Step 3 of 3
\end{verbatim}
When you use Service Builder, you implement the services in the service
module. Because your application's projects are
\href{/docs/7-2/appdev/-/knowledge_base/a/invoking-local-services}{components},
you can reference your service layer from your web module.
You'll implement services for guestbooks and entries in the
\texttt{guestbook-service} module's \texttt{GuestbookLocalServiceImpl}
and \texttt{GuestbookEntryLocalServiceImpl}, respectively.
Follow these steps to implement services for guestbooks in
\texttt{GuestbookLocalServiceImpl}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the \texttt{com.liferay.docs.guestbook.service.impl} package, open
\texttt{GuestbookLocalServiceImpl}. Then add this
\texttt{addGuestbook} method:
\begin{verbatim}
public Guestbook addGuestbook(long userId, String name,
ServiceContext serviceContext) throws PortalException {
long groupId = serviceContext.getScopeGroupId();
User user = userLocalService.getUserById(userId);
Date now = new Date();
validate(name);
long guestbookId = counterLocalService.increment();
Guestbook guestbook = guestbookPersistence.create(guestbookId);
guestbook.setUuid(serviceContext.getUuid());
guestbook.setUserId(userId);
guestbook.setGroupId(groupId);
guestbook.setCompanyId(user.getCompanyId());
guestbook.setUserName(user.getFullName());
guestbook.setCreateDate(serviceContext.getCreateDate(now));
guestbook.setModifiedDate(serviceContext.getModifiedDate(now));
guestbook.setName(name);
guestbook.setExpandoBridgeAttributes(serviceContext);
guestbookPersistence.update(guestbook);
return guestbook;
}
\end{verbatim}
This method adds a guestbook to the database. It retrieves metadata
from the environment (such as the current user's ID, the group ID,
etc.), along with data passed from the user. It validates this data
and uses it to construct a \texttt{Guestbook} object. The method then
persists this object to the database and returns the object. You
implement the business logic here because Service Builder already
generated the model and all the code that maps that model to the
database.
\item
Add the methods for getting \texttt{Guestbook} objects:
\begin{verbatim}
public List getGuestbooks(long groupId) {
return guestbookPersistence.findByGroupId(groupId);
}
public List getGuestbooks(long groupId, int start, int end,
OrderByComparator obc) {
return guestbookPersistence.findByGroupId(groupId, start, end, obc);
}
public List getGuestbooks(long groupId, int start, int end) {
return guestbookPersistence.findByGroupId(groupId, start, end);
}
public int getGuestbooksCount(long groupId) {
return guestbookPersistence.countByGroupId(groupId);
}
\end{verbatim}
These call the finders you generated with Service Builder. The first
method retrieves a list of guestbooks from the Site specified by
\texttt{groupId}. The next two methods get paginated lists, optionally
in a particular order. The final method gives you the total number of
guestbooks for a given Site.
\item
Finally, add the guestbook validator method:
\begin{verbatim}
protected void validate(String name) throws PortalException {
if (Validator.isNull(name)) {
throw new GuestbookNameException();
}
}
\end{verbatim}
This method uses Liferay DXP's \texttt{Validator} to make sure the
user entered text for the guestbook name.
\item
Press {[}CTRL{]}+{[}SHIFT{]}+O to organize imports and select the
following classes when prompted:
\begin{itemize}
\tightlist
\item
\texttt{java.util.List}
\item
\texttt{java.util.Date}
\item
\texttt{com.liferay.portal.kernel.util.Validator}
\end{itemize}
\end{enumerate}
Now you're ready to implement services for entries in
\texttt{GuestbookEntryLocalServiceImpl}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the \texttt{com.liferay.docs.guestbook.service.impl} package, open
\texttt{GuestbookEntryLocalServiceImpl}. Add this \texttt{addEntry}
method:
\begin{verbatim}
public GuestbookEntry addGuestbookEntry(long userId, long guestbookId, String name,
String email, String message, ServiceContext serviceContext)
throws PortalException {
long groupId = serviceContext.getScopeGroupId();
User user = userLocalService.getUserById(userId);
Date now = new Date();
validate(name, email, message);
long entryId = counterLocalService.increment();
GuestbookEntry entry = guestbookEntryPersistence.create(entryId);
entry.setUuid(serviceContext.getUuid());
entry.setUserId(userId);
entry.setGroupId(groupId);
entry.setCompanyId(user.getCompanyId());
entry.setUserName(user.getFullName());
entry.setCreateDate(serviceContext.getCreateDate(now));
entry.setModifiedDate(serviceContext.getModifiedDate(now));
entry.setExpandoBridgeAttributes(serviceContext);
entry.setGuestbookId(guestbookId);
entry.setName(name);
entry.setEmail(email);
entry.setMessage(message);
guestbookEntryPersistence.update(entry);
// Calls to other Liferay frameworks go here
return entry;
}
\end{verbatim}
Like the \texttt{addGuestbook} method, \texttt{addGuestbookEntry}
takes data from the current context along with data the user entered,
validates it, and creates a model object. That object is then
persisted to the database and returned.
\item
Add this \texttt{updateGuestbookEntry} method:
\begin{verbatim}
public GuestbookEntry updateGuestbookEntry(long userId, long guestbookId,
long entryId, String name, String email, String message,
ServiceContext serviceContext)
throws PortalException, SystemException {
Date now = new Date();
validate(name, email, message);
GuestbookEntry entry =
guestbookEntryPersistence.findByPrimaryKey(entryId);
User user = userLocalService.getUserById(userId);
entry.setUserId(userId);
entry.setUserName(user.getFullName());
entry.setModifiedDate(serviceContext.getModifiedDate(now));
entry.setName(name);
entry.setEmail(email);
entry.setMessage(message);
entry.setExpandoBridgeAttributes(serviceContext);
guestbookEntryPersistence.update(entry);
// Integrate with Liferay frameworks here.
return entry;
}
\end{verbatim}
This method first retrieves the entry and updates its data to reflect
what the user submitted, including its date modified.
\item
Add two \texttt{deleteGuestbookEntry} methods:
\begin{verbatim}
public GuestbookEntry deleteGuestbookEntry(GuestbookEntry entry)
throws PortalException {
guestbookEntryPersistence.remove(entry);
return entry;
}
public GuestbookEntry deleteGuestbookEntry(long entryId) throws PortalException {
GuestbookEntry entry =
guestbookEntryPersistence.findByPrimaryKey(entryId);
return deleteGuestbookEntry(entry);
}
\end{verbatim}
These methods delete entries using the \texttt{entryId} or the object.
If you supply the \texttt{entryId}, the object is retrieved and passed
to the method that deletes the object.
\item
Add the methods for getting \texttt{GuestbookEntry} objects:
\begin{verbatim}
public List getGuestbookEntries(long groupId, long guestbookId) {
return guestbookEntryPersistence.findByG_G(groupId, guestbookId);
}
public List getGuestbookEntries(long groupId, long guestbookId,
int start, int end) throws SystemException {
return guestbookEntryPersistence.findByG_G(groupId, guestbookId, start,
end);
}
public List getGuestbookEntries(long groupId, long guestbookId,
int start, int end, OrderByComparator obc) {
return guestbookEntryPersistence.findByG_G(groupId, guestbookId, start,
end, obc);
}
public GuestbookEntry getGuestbookEntry(long entryId) throws PortalException {
return guestbookEntryPersistence.findByPrimaryKey(entryId);
}
public int getGuestbookEntriesCount(long groupId, long guestbookId) {
return guestbookEntryPersistence.countByG_G(groupId, guestbookId);
}
\end{verbatim}
These methods, like the getters in \texttt{GuestbookLocalServiceImpl},
call the finders you generated with Service Builder. These
\texttt{getGuestbookEntries*} methods, however, retrieve entries from
a specified Guestbook and Site. The first method gets a list of
entries. The next method gets a paginated list. The third method sorts
the paginated list, and the last method gets the total number of
entries as an integer.
\item
Add the \texttt{validate} method:
\begin{verbatim}
protected void validate(String name, String email, String entry)
throws PortalException {
if (Validator.isNull(name)) {
throw new GuestbookEntryNameException();
}
if (!Validator.isEmailAddress(email)) {
throw new GuestbookEntryEmailException();
}
if (Validator.isNull(entry)) {
throw new GuestbookEntryMessageException();
}
}
\end{verbatim}
This method makes sure the user entered relevant data when creating an
entry.
\item
Press {[}CTRL{]}+{[}SHIFT{]}+O to organize imports and select the
following classes when prompted:
\begin{itemize}
\tightlist
\item
\texttt{java.util.List}
\item
\texttt{java.util.Date}
\item
\texttt{com.liferay.portal.kernel.util.Validator}
\end{itemize}
\end{enumerate}
Nice work! These local service methods implement the services that are
referenced in the portlet class.
\section{Updating Generated Classes}\label{updating-generated-classes}
Now that you've implemented the service methods, you must make them
available to the rest of your application. To do this, run
\texttt{buildService} again:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In \emph{Gradle Tasks} → \emph{guestbook-service} → \emph{build},
right-click \texttt{buildService} and select \emph{Run Gradle Tasks}.
In the utility classes, Service Builder populates calls to your newly
created service methods.
\item
In the Project Explorer, right-click the \texttt{guestbook-service}
module and select \emph{Refresh}. Repeat this step for the
\texttt{guestbook-api} module. This ensures that the changes made by
Service Builder show up in Liferay Dev Studio DXP.
\item
In the Project Explorer, right-click the \texttt{guestbook-service}
module and select \emph{Gradle} → \emph{Refresh Gradle Project}.
Repeat this step for the \texttt{guestbook-api} module. This ensures
that your modules' Gradle dependencies are up to date.
\end{enumerate}
\noindent\hrulefill
\textbf{Tip:} If something goes awry when working with Service Builder,
repeat these steps to run Service Builder again and refresh your API and
service modules.
\noindent\hrulefill
Excellent! Your new back-end has been generated. Now it's time to create
a web application that uses it.
\chapter{Building the Web Front-End}\label{building-the-web-front-end}
You now have a back-end: you created a \texttt{service.xml} file to
define your application's data model, and generated the back-end code
(the model, service, and persistence layers) with Service Builder. You
also added service methods using the appropriate extension points: your
entities' \texttt{*LocalServiceImpl} classes. Now you can add a
front-end to match new back-end, creating a fully functional
application.
You'll create two portlets: the Guestbook portlet for users to use to
add entries and in a later step, a Guestbook Admin portlet for
administrators to add Guestbooks.
Starting with this step,
\href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/guestbook/04-web-front-end}{source
code is provided} in case you get stuck.
Ready to begin?
Let's Go!{}
\chapter{Creating the Web Project}\label{creating-the-web-project}
\begin{verbatim}
Building the Web Front-End
Step 1 of 11
\end{verbatim}
Your first step is to create another Liferay Module Project. Modules are
the core building blocks of Liferay DXP applications. Every application
is made from one or more modules. Each module encapsulates a functional
piece of an application. Multiple modules form a complete application.
Modules can be web modules or \href{https://www.osgi.org/}{OSGi}
modules. Since you'll be creating a Liferay MVC Portlet, you'll create
an OSGI module. The OSGi container in Liferay DXP can run any OSGi
module. Each module is packaged as a JAR file that contains a manifest
file. The manifest is needed for the container to recognize the module.
Technically, a module that contains only a manifest is still valid. Of
course, such a module wouldn't be very interesting.
You already created Service Builder modules. Now you'll create your MVC
Portlet module. For the purpose of this tutorial, you'll create your
modules inside your Liferay Workspace.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In Liferay Dev Studio DXP, select \emph{File} → \emph{New} →
\emph{Liferay Module Project}.
\item
Complete the first screen of the wizard with the following
information:
\begin{figure}
\centering
\includegraphics{./images/new-module-project.png}
\caption{Complete the New Module Project wizard.}
\end{figure}
\begin{itemize}
\tightlist
\item
Enter \texttt{guestbook-web} for the Project name.
\item
Use the \emph{Gradle} Build type.
\item
The Liferay version is \emph{7.2}.
\item
Select \texttt{mvc-portlet} for the Project Template.
\end{itemize}
Click \emph{Next}.
\item
On the second screen of the wizard, enter \texttt{Guestbook} for the
component class name, and \texttt{com.liferay.docs.guestbook.portlet}
for the package name. Click \emph{Finish}.
\end{enumerate}
Note that it may take a while for Dev Studio DXP to create your project,
because Gradle downloads your project's dependencies for you during
project creation. Once this is done, you have a module project named
\texttt{guestbook-web}. The \texttt{mvc-portlet} template configured the
project with the proper dependencies and generated all the files you
need to get started:
\begin{itemize}
\tightlist
\item
The portlet class (in the package you specified)
\item
JSP files (in \texttt{/src/main/resources})
\item
Language properties (also in \texttt{/src/main/resources})
\end{itemize}
Your new module project is a \emph{portlet} application. You'll learn
what that is in a moment, but first there's some housekeeping to do.
In larger projects, it is important to have all of your files and
modules well organized. Since the \texttt{guestbook-web} module really
goes with your Service Builder modules, it should be in the
\texttt{guestbook} folder.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the \emph{Project Explorer}, right-click on \texttt{guestbook-web}
and select \emph{Move}.
\item
In the window that appears, click \emph{Browse}, choose the
\texttt{guestbook} folder and then click \emph{OK}.
\end{enumerate}
Your \texttt{guestbook-web} folder now appears in the structure with the
other modules.
\begin{figure}
\centering
\includegraphics{./images/guestbook-refactor.png}
\caption{After you move it, all of your modules are in the same
folder..}
\end{figure}
\noindent\hrulefill
\textbf{Note:} Sometimes Eclipse refuses to move your project. If that
happens, close Eclipse, use your operating system's file manager to move
the \texttt{guestbook-web} folder into the \texttt{guestbook} folder,
and then restart Eclipse.
\noindent\hrulefill
You're now ready to begin writing your front-end, but first some
explanation is in order.
\section{What is a Portlet?}\label{what-is-a-portlet}
Web applications can be simple or complex: they might display an article
or calculate your taxes. These applications run on a \emph{platform}
that provides application developers the building blocks they need to
make applications.
\begin{figure}
\centering
\includegraphics{./images/portlet-applications.png}
\caption{Many Liferay applications can run at the same time on the same
page.}
\end{figure}
Liferay DXP provides a platform that contains common features needed by
today's applications, including user management, security, user
interfaces, services, and more. Portlets are one of those basic building
blocks. Often a web application takes up the entire page. Portlets can
do this or share the page with many applications at the same time.
Liferay DXP's framework takes this into account at every step.
\section{What is a Component?}\label{what-is-a-component}
Liferay MVC Portlets are \emph{Components}. If a module (sometimes also
called a \emph{bundle}) encapsulates pieces of your application, a
component is the object that contains the core functionality. A
Component is managed by a component framework or container. Components
are deployed inside modules, and they're created, started, stopped, and
destroyed as needed by the container. What a perfect model for a web
application! It can be made available only when needed, and when it's
not, the container can make sure it doesn't use resources needed by
other components.
In this case, you created a Declarative Services (DS) component. With
Declarative Services, you declare that an object is a component, and you
define data about the component so the container knows how to manage it.
A default configuration was created for you; you'll examine it later.
\section{Deploying the Application}\label{deploying-the-application}
Even though all you've done is generate it, the \texttt{guestbook-web}
project is ready to be built and deployed.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Make sure that your server is running, and if it isn't, select it in
Dev Studio DXP's Servers pane and click the start button
(\includegraphics{./images/icon-start-server.png}).
\item
After it starts, drag and drop the \texttt{guestbook-web} project from
the Project Explorer to the server.
\begin{figure}
\centering
\includegraphics{./images/deploy-module.png}
\caption{Drag and drop the module.}
\end{figure}
\item
Open a browser and navigate to Liferay DXP
(\url{http://localhost:8080} by default).
If this is your first time starting Liferay DXP, you'll go through a
short wizard to set up your server. In this wizard, make sure you use
the default database (Hypersonic). Although this database isn't
intended for production use, it works fine for development and
testing.
\item
Click the menu button at the top left and select \emph{Site Builder} →
\emph{Pages}.
\item
Click the \includegraphics{./images/icon-add.png} button at the top
right to add a Public Page.
\item
Choose \emph{Widget Page} and name it \emph{Guestbook}.
\item
Choose the \emph{One Column} layout and click \emph{Save}.
\item
Click \emph{Go to Site} on the left, and then navigate to your new
Guestbook page.
\item
Click \emph{Add} (\includegraphics{./images/icon-add-app.png}) in the
upper right hand corner.
\item
Select \emph{Widgets}. In the Applications list, your application
appears in the Sample category. Its name is \texttt{Guestbook}.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/default-guestbook-application.png}
\caption{This is your new page with the Guestbook application that you
created.}
\end{figure}
Now you're ready to jump in and start developing your Guestbook portlet.
\chapter{Defining the Component Metadata
Properties}\label{defining-the-component-metadata-properties}
\begin{verbatim}
Building the Web Front-End
Step 2 of 11
\end{verbatim}
When users add applications to a page, they pick them from a list of
\emph{display categories}.
\begin{figure}
\centering
\includegraphics{./images/display-categories.png}
\caption{Users choose applications from a list of display categories.}
\end{figure}
A portlet's display category is defined in its component class as a
metadata property. Since the Guestbook portlet lets users communicate
with each other, you'll add it to the Social category. Only one
Guestbook portlet should be added to a page, so you'll also define it as
a \emph{non-instanceable} portlet. Such a portlet can appear only once
on a page or Site, depending on its scope.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the \texttt{GuestbookPortlet} class and update the component
class metadata properties to match this configuration:
\begin{verbatim}
@Component(
immediate = true,
property = {
"com.liferay.portlet.display-category=category.social",
"com.liferay.portlet.instanceable=false",
"com.liferay.portlet.scopeable=true",
"javax.portlet.display-name=Guestbook",
"javax.portlet.expiration-cache=0",
"javax.portlet.init-param.template-path=/",
"javax.portlet.init-param.view-template=/guestbook/view.jsp",
"javax.portlet.resource-bundle=content.Language",
"javax.portlet.security-role-ref=power-user,user",
"javax.portlet.supports.mime-type=text/html"
},
service = Portlet.class
)
\end{verbatim}
\end{enumerate}
The \texttt{com.liferay.portlet.display-category=category.social}
property sets the Guestbook portlet's display category to \emph{Social}.
The \texttt{com.liferay.portlet.instanceable=false} property specifies
that the Guestbook portlet is non-instanceable, so only one instance of
the portlet can be added to a page. In the property
\texttt{javax.portlet.init-param.view-template}, you also update the
location of the main \texttt{view.jsp} to a folder in
\texttt{src/main/resources/META-INF/resources} called
\texttt{/guestbook}. You'll wind up creating two folders there for the
two different portlets you'll create: \texttt{guestbook} and
\texttt{guestbook-admin}. For now, just create the \texttt{guestbook}
folder:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open \texttt{src/main/resources}, then open \texttt{META-INF}.
Right-click on the \texttt{resources} folder and select \emph{New} →
\emph{Folder}.
\item
Name the folder \emph{guestbook} and hit \emph{Enter} (or click OK).
\item
Drag \texttt{view.jsp} and drop it onto the \texttt{guestbook} folder
to move it there.
\item
Open \texttt{view.jsp} and modify the path to \texttt{init.jsp} to
include it from the parent folder:
\begin{verbatim}
<%@ include file="../init.jsp" %>
\end{verbatim}
\end{enumerate}
Since you edited the portlet's metadata, you must remove and re-add the
portlet to the page before continuing:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to \texttt{localhost:8080} in your web browser.
\item
Sign in to your administrative account.
\item
The Guestbook portlet now shows an error on the page. Click its
portlet menu (at the top-right of the portlet), then select
\emph{Remove} and click \emph{OK} to confirm.
\item
Open the \emph{Add} menu and select \emph{Widgets}.
\item
Open the \emph{Social} category and drag and drop the \emph{Guestbook}
widget onto the page.
\end{enumerate}
Great! Now the Guestbook portlet appears in an appropriate category.
Though you were able to add it to the page before, the user experience
is better.
\chapter{Creating Portlet Keys}\label{creating-portlet-keys}
\begin{verbatim}
Building the Web Front-End
Step 3 of 11
\end{verbatim}
\texttt{PortletKeys} manage important things like the portlet name or
other repeatable, commonly used variables in one place. This way, if you
must change the portlet's name, you can do it in one place and then
reference it in every class that needs it. Keys are created in a
\texttt{PortletKeys} class and then referenced in a component property.
Follow these steps to create your application's \texttt{PortletKeys}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the \texttt{com.liferay.docs.guestbook.constants} package.
\item
Open \texttt{GuestbookPortletKeys} and make sure there's a public,
static, final String called \texttt{GUESTBOOK} with a value of
\texttt{com\_liferay\_docs\_guestbook\_portlet\_GuestbookPortlet}:
\begin{verbatim}
public static final String GUESTBOOK =
"com_liferay_docs_guestbook_portlet_GuestbookPortlet";
\end{verbatim}
\item
Save the file.
\item
In your \texttt{guestbook-web} module, open the
\texttt{GuestbookPortlet} class and update the component class
metadata properties by adding one new property:
\begin{verbatim}
"javax.portlet.name=" + GuestbookPortletKeys.GUESTBOOK,
\end{verbatim}
Note that you need the trailing comma if you've added the property to
the middle of the list. If you've added it to the end of the last,
leave it off (but add a trailing comma to the prior property!).
\item
Save \texttt{GuestbookPortlet}.
\end{enumerate}
Nice job!
Next, you'll integrate your application with the back-end you generated
with Service Builder.
\chapter{Integrating the Back-end}\label{integrating-the-back-end}
\begin{verbatim}
Building the Web Front-End
Step 4 of 11
\end{verbatim}
To use your Service Builder-generated back-end in your front-end, you
must tell your front-end project that the back-end exists and where to
find it.
For the web module to access the generated services, you must make it
aware of the API and service modules:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open \texttt{guestbook-web}'s \texttt{build.gradle} file and add these
dependencies:
\begin{verbatim}
compileOnly project(":modules:guestbook:guestbook-api")
compileOnly project(":modules:guestbook:guestbook-service")
\end{verbatim}
\item
Save the file. Right-click on the \texttt{guestbook-web} project and
select \emph{Gradle} → \emph{Refresh Gradle Project}.
\item
Now you must add \emph{references} to the Service Builder services you
need. To do this, add them as class variables with \texttt{@Reference}
annotations on their setter methods. Open \texttt{GuestbookPortlet}
and add these references to the bottom of the file:
\begin{verbatim}
@Reference
private GuestbookEntryLocalService _guestbookEntryLocalService;
@Reference
private GuestbookLocalService _guestbookLocalService;
\end{verbatim}
Note that it's Liferay's code style to add class variables this way.
The \texttt{@Reference} annotation causes Liferay's OSGi container to
inject references to your generated services so you can use them.
\item
Press Ctrl-Shift-O to add the import you need:
\begin{itemize}
\tightlist
\item
\texttt{org.osgi.service.component.annotations.Reference}
\end{itemize}
\end{enumerate}
Now you're ready to begin building your front-end.
\chapter{Creating an Add Entry
Button}\label{creating-an-add-entry-button}
\begin{verbatim}
Building the Web Front-End
Step 5 of 11
\end{verbatim}
A guestbook application is pretty simple, right? People come to your
site and post their names and brief messages. Others can read these
entries and post their own.
When you created your project, it generated a file named
\texttt{view.jsp}, which you've already moved to
\texttt{src/main/resources/META-INF/resources/guestbook} folder. This
file contains the default view for users when the portlet is added to
the page. Right now it contains sample content:
\begin{verbatim}
<%@ include file="../init.jsp" %>
\end{verbatim}
First, \texttt{view.jsp} imports \texttt{init.jsp}. By Liferay
convention, we declare imports and tag library declarations in an
\texttt{init.jsp} file. The other JSP files in the application import
\texttt{init.jsp}. This reserves JSP dependency management to a single
file.
Besides importing \texttt{init.jsp}, \texttt{view.jsp} displays a
message defined by a language key. This key and its value are declared
in your project's
\texttt{src/main/resources/content/Language.properties} file.
It's time to start developing the Guestbook application. First, users
need a way to add a Guestbook entry. In \texttt{view.jsp}, follow these
steps to add this button:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Remove everything under the include for \texttt{init.jsp}.
\item
Below the include, add the following
\href{http://alloyui.com/}{AlloyUI} tags to display an Add Entry
button inside of a button row:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
You can use \texttt{aui} tags in \texttt{view.jsp} since
\texttt{init.jsp} declares the AlloyUI tag library by default (as well
as other important imports and tags):
\begin{verbatim}
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
\end{verbatim}
Your application now displays a button instead of a message, but the
button doesn't do anything. Next, you'll create a URL for your button.
\begin{figure}
\centering
\includegraphics{./images/guestbook-new-button.png}
\caption{Your new button is awesome, but it doesn't work yet.}
\end{figure}
\chapter{Generating Portlet URLs}\label{generating-portlet-urls}
\begin{verbatim}
Building the Web Front-End
Step 6 of 11
\end{verbatim}
Since users can place multiple portlets on a single page, you have no
idea what other portlets may share a page with yours. This means that
you can't define URLs for various functions in your application like you
otherwise would.
For example, consider a Calendar application that a user puts on the
same page as a Blog application. To implement the functionality for
deleting calendar events and blog entries in the respective application,
both application developers append the \texttt{del} parameter to the URL
and give it a primary key value so the application can look up and
delete the calendar event or blog entry. Since both applications read
this parameter, their delete functionality clashes.
System-generated URLs prevent this. If the system generates a unique URL
for each piece of functionality, multiple applications can coexist in
perfect harmony.
In \texttt{view.jsp}, follow these steps to create system-generated URLs
in your portlet:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add these tags below
\texttt{\textless{}\%@\ include\ file="../init.jsp"\ \%\textgreater{}},
but above the \texttt{\textless{}aui:button-row\textgreater{}} tag:
\begin{verbatim}
\end{verbatim}
\item
Add this attribute to the \texttt{\textless{}aui:button\textgreater{}}
tag, before \texttt{value="Add\ Entry"}:
\begin{verbatim}
onClick="<%= addEntryURL.toString() %>"
\end{verbatim}
Your \texttt{view.jsp} page should now look like this:
\begin{verbatim}
<%@ include file="/init.jsp" %>
\end{verbatim}
\end{enumerate}
The \texttt{\textless{}portlet:renderURL\textgreater{}} tag's
\texttt{var} attribute creates the \texttt{addEntryURL} variable to hold
the system-generated URL. The
\texttt{\textless{}portlet:param\textgreater{}} tag defines a URL
parameter to append to the URL. In this example, a URL parameter named
\texttt{mvcPath} with a value of \texttt{/edit\_entry.jsp} is appended
to the URL.
Note that your \texttt{GuestbookPortlet} class (located in your
\texttt{guestbook-web} module's
\texttt{com.liferay.docs.guestbook.portlet} package) extends Liferay's
\texttt{MVCPortlet} class. In a
\href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay
MVC portlet}, the \texttt{mvcPath} URL parameter indicates a page within
your portlet. To navigate to another page in your portlet, use a portal
URL with the parameter \texttt{mvcPath} to link to the specific page.
In the example above, you created a \texttt{renderURL} that points to
your application's \texttt{edit\_entry.jsp} page, which you haven't yet
created. Note that using an AlloyUI button to follow the generated URL
isn't required. You can use any HTML construct that contains a link.
Users can click your button to access your application's
\texttt{edit\_entry.jsp} page. This currently produces an error since no
\texttt{edit\_entry.jsp} exists yet. Creating \texttt{edit\_entry.jsp}
is your next step.
\chapter{Linking to Another Page}\label{linking-to-another-page}
\begin{verbatim}
Building the Web Front-End
Step 7 of 11
\end{verbatim}
In the same folder your \texttt{view.jsp} is in, create the
\texttt{edit\_entry.jsp} file:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click your project's
\texttt{src/main/resources/META-INF/resources/guestbook} folder and
choose \emph{New} → \emph{File}.
\item
Name the file \texttt{edit\_entry.jsp} and click \emph{Finish}.
\item
Add this line to the top of the file:
\begin{verbatim}
<%@ include file="../init.jsp" %>
\end{verbatim}
Remember, it's a best practice to add all JSP imports and tag library
declarations to a single file that's imported by your application's
other JSP files. For \texttt{edit\_entry.jsp}, you need these imports
to access the portlet tags that create URLs and the Alloy tags that
create the form.
\item
Next, you need a scriptlet that helps determine the function the user
accessed. You named this JSP \texttt{edit\_entry.jsp} because it's
used both for adding and editing. Add this scriptlet to add logic for
determining which function the user wants:
\begin{verbatim}
<%
long entryId = ParamUtil.getLong(renderRequest, "entryId");
GuestbookEntry entry = null;
if (entryId > 0) {
entry = GuestbookEntryLocalServiceUtil.getGuestbookEntry(entryId);
}
long guestbookId = ParamUtil.getLong(renderRequest, "guestbookId");
%>
\end{verbatim}
If an \texttt{entryId} greater than \texttt{0} is found in the
request, editing a \texttt{GuestbookEntry} is assumed. Otherwise, the
user is adding.
\item
You'll create two URLs: one in the next step to submit the form and
one in this step to go back to \texttt{view.jsp}. To create the URL to
go back to \texttt{view.jsp}, add the following tag below the first
line you added:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
Next, you must create a new URL for submitting the form. This is a
different kind of URL, for it triggers a portlet action.
\chapter{Forms and Action URLs}\label{forms-and-action-urls}
\begin{verbatim}
Building the Web Front-End/p>
Step 8 of 11
\end{verbatim}
Recall that portlets run in a portion of a page, and a page can contain
multiple portlets. Because of this, portlets have \emph{phases} of
operation. Here, you'll learn about the most important two. The first
phase is the one you've already used: the \emph{render} phase. All this
means is that the portlet draws itself, using the JSPs you write for it.
The other phase is called the \emph{action} phase. This phase runs once,
when a user triggers a portlet action. The portlet performs whatever
action the user triggered, such as performing a search or adding a
record to a database. Then the portlet goes back to the render phase and
re-renders itself according to its new state.
\section{Action URLs}\label{action-urls}
To save a guestbook entry, you must trigger a portlet action. For this,
you'll create an action URL.
Add the following tag in \texttt{edit\_entry.jsp} after the closing
\texttt{\textless{}/portlet:renderURL\textgreater{}} tag:
\begin{verbatim}
\end{verbatim}
You now have the two required URLs for your form.
\section{Forms}\label{forms}
The form for creating guestbook entries has three fields: one for the
name of the person submitting the entry, one for the person's email
address, and one for the entry itself.
Add the following tags to the end of your \texttt{edit\_entry.jsp} file:
\begin{verbatim}
\end{verbatim}
Liferay uses its Alloy UI tag library to create forms.
Save \texttt{edit\_entry.jsp} and redeploy your application. If you
refresh the page and click the \emph{Add Entry} button, your form
appears. If you click the \emph{Cancel} button, you go back to
\texttt{view.jsp}, but don't try the \emph{Save} button yet. You haven't
yet created the action that saves a guestbook entry, so clicking
\emph{Save} produces an error.
\begin{figure}
\centering
\includegraphics{./images/first-guestbook-portlet-edit-entry.png}
\caption{This is the Guestbook application's form for adding entries.}
\end{figure}
Implementing portlet actions (what happens when the user clicks
\emph{Save} or \emph{Delete}) is your next task.
\chapter{Implementing Portlet
Actions}\label{implementing-portlet-actions}
\begin{verbatim}
Building the Web Front-End
Step 9 of 11
\end{verbatim}
When users submit the form, your application stores the form data for
display in the guestbook. This is where you call the back-end you
generated to store the data for later retrieval in the database.
To make your portlet do anything other than re-render itself, you must
implement portlet actions. An action defines some processing, usually
based on user input, that the portlet must perform before it renders
itself. In the case of the guestbook portlet, the action you'll
implement next saves a guestbook entry that a user typed into the form.
Saved guestbook entries can be retrieved and displayed later.
Since you're using Liferay's MVC Portlet framework, you have an easy way
to implement actions. Portlet actions are implemented in the portlet
class, which is the controller. In the form you just created, you made
an action URL, and you called it \texttt{addEntry}. To create a portlet
action, you create a method in the portlet class with the same name.
\texttt{MVCPortlet} calls that method when a user triggers its matching
URL.
\section{Creating an Add Entry
Action}\label{creating-an-add-entry-action}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open \texttt{GuestbookPortlet}.
\item
Create a method with the following signature:
\begin{verbatim}
public void addEntry(ActionRequest request, ActionResponse response) {
}
\end{verbatim}
\item
Press {[}CTRL{]}+{[}SHIFT{]}+O to organize imports and import the
required \texttt{javax.portlet.ActionRequest} and
\texttt{javax.portlet.ActionResponse} classes.
\end{enumerate}
You've now created a portlet action. It doesn't do anything, but at
least you won't get an error now if you submit your form. Next, you
should make the action save the form data.
The following method implements adding a guestbook entry using the
back-end you generated with Service Builder:
\begin{verbatim}
public void addEntry(ActionRequest request, ActionResponse response)
throws PortalException {
ServiceContext serviceContext = ServiceContextFactory.getInstance(
GuestbookEntry.class.getName(), request);
String userName = ParamUtil.getString(request, "name");
String email = ParamUtil.getString(request, "email");
String message = ParamUtil.getString(request, "message");
long guestbookId = ParamUtil.getLong(request, "guestbookId");
long entryId = ParamUtil.getLong(request, "entryId");
if (entryId > 0) {
try {
_guestbookEntryLocalService.updateGuestbookEntry(
serviceContext.getUserId(), guestbookId, entryId, userName,
email, message, serviceContext);
response.setRenderParameter(
"guestbookId", Long.toString(guestbookId));
}
catch (Exception e) {
System.out.println(e);
PortalUtil.copyRequestParameters(request, response);
response.setRenderParameter(
"mvcPath", "/guestbook/edit_entry.jsp");
}
}
else {
try {
_guestbookEntryLocalService.addGuestbookEntry(
serviceContext.getUserId(), guestbookId, userName, email,
message, serviceContext);
response.setRenderParameter(
"guestbookId", Long.toString(guestbookId));
}
catch (Exception e) {
System.out.println(e);
PortalUtil.copyRequestParameters(request, response);
response.setRenderParameter(
"mvcPath", "/guestbook/edit_entry.jsp");
}
}
}
\end{verbatim}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Replace your existing \texttt{addEntry} method with the above method.
\item
Press {[}CTRL{]}+{[}SHIFT{]}+O to organize imports.
\end{enumerate}
This \texttt{addEntry} method gets the name, message, and email fields
that the user submits in the JSP and passes them to the service to be
stored as entry data. It also gets a
\href{/docs/7-2/frameworks/-/knowledge_base/f/understanding-servicecontext}{\texttt{ServiceContext}}
so information about the request's current context can be retrieved,
such as the ID of the current user. The \texttt{if-else} logic checks
whether there's an existing \texttt{entryId}. If there is, the
\texttt{update} service method is called, and if not, the \texttt{add}
service method is called. In both cases, it sets a render parameter with
the Guestbook ID so the application can display the guestbook's entries
after this one has been added. This is all done in \texttt{try...catch}
statements. Note the setting of the \texttt{mvcPath} render parameter to
direct processing to the proper JSP based on what happens.
\section{Creating a Delete Entry
Action}\label{creating-a-delete-entry-action}
Next, create an action that deletes an entry:
\begin{verbatim}
public void deleteEntry(ActionRequest request, ActionResponse response) throws PortalException {
long entryId = ParamUtil.getLong(request, "entryId");
long guestbookId = ParamUtil.getLong(request, "guestbookId");
ServiceContext serviceContext = ServiceContextFactory.getInstance(
GuestbookEntry.class.getName(), request);
try {
response.setRenderParameter(
"guestbookId", Long.toString(guestbookId));
_guestbookEntryLocalService.deleteGuestbookEntry(entryId);
}
catch (Exception e) {
Logger.getLogger(GuestbookPortlet.class.getName()).log(
Level.SEVERE, null, e);
}
}
\end{verbatim}
This action accepts an \texttt{entryId} from the request and calls the
service to delete it.
But there's something missing, isn't there? These methods expect a
\texttt{guestbookId} to be in the request, so the
\texttt{GuestbookEntry} can be connected to its \texttt{Guestbook}.
You'll create that next, as well as a mechanism for viewing guestbook
entries.
\chapter{Displaying Guestbook
Entries}\label{displaying-guestbook-entries}
\begin{verbatim}
Building the Web Front-End
Step 10 of 11
\end{verbatim}
To display guestbook entries, you must do the reverse of what you did to
store them: retrieve them the database, loop through them, and present
them on the page. To do this, you must override the default MVC Portlet
\texttt{render} method so you can tell your portlet how to render
itself.
\section{Rendering the Portlet}\label{rendering-the-portlet}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following \texttt{render} method to \texttt{GuestbookPortlet}:
\begin{verbatim}
@Override
public void render(RenderRequest renderRequest, RenderResponse renderResponse)
throws IOException, PortletException {
try {
ServiceContext serviceContext = ServiceContextFactory.getInstance(
Guestbook.class.getName(), renderRequest);
long groupId = serviceContext.getScopeGroupId();
long guestbookId = ParamUtil.getLong(renderRequest, "guestbookId");
List guestbooks = _guestbookLocalService.getGuestbooks(
groupId);
if (guestbooks.isEmpty()) {
Guestbook guestbook = _guestbookLocalService.addGuestbook(
serviceContext.getUserId(), "Main", serviceContext);
guestbookId = guestbook.getGuestbookId();
}
if (guestbookId == 0) {
guestbookId = guestbooks.get(0).getGuestbookId();
}
renderRequest.setAttribute("guestbookId", guestbookId);
}
catch (Exception e) {
throw new PortletException(e);
}
super.render(renderRequest, renderResponse);
}
\end{verbatim}
This \texttt{render} method checks for guestbooks in the current Site.
If there aren't any, it creates one. The \texttt{guestbookId} that it
has (either the first one or one that has been selected in
functionality you haven't written yet) is set in the request object so
that it becomes the current guestbook.
\item
Press {[}CTRL{]}+{[}SHIFT{]}+O to organize imports and then save the
file.
\end{enumerate}
\noindent\hrulefill
Note: When you are prompted to choose imports, here are some guidelines:
\begin{itemize}
\item
Always use \texttt{org.osgi...} packages instead of
\texttt{aQute.bnd...}
\item
Generally use \texttt{java.util...} or \texttt{javax.portlet...}
packages.
\item
You never use \texttt{java.awt...} in this project.
\item
Only use \texttt{com.liferay...} when it is for a Liferay specific
implementation or your custom implementation of a concept.
\end{itemize}
For example:
\begin{itemize}
\item
If you are given the choice between \texttt{javax.portlet.Portlet} and
\texttt{com.liferay.portlet.Portlet} choose
\texttt{javax.portlet.Portlet}.
\item
If you are given the choice between \texttt{org.osgi.component} and
\texttt{aQute.bnd.annotation.component} choose
\texttt{org.osgi.component}
\end{itemize}
If at some point you think you chose an incorrect import, but you're not
sure what it might be, you can erase all of the imports from the file
and press {[}CTRL{]}+{[}SHIFT{]}+O again and see if you can identify
where you went wrong.
\noindent\hrulefill
Now that you have your controller preparing your data for display, your
next step is to implement the view so users can see guestbook entries.
\section{Displaying Guestbook
Entries}\label{displaying-guestbook-entries-1}
Liferay's development framework makes it easy to loop through data and
display it nicely to the end user. You'll use a Liferay UI construct
called \emph{Search Container} to make this happen.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Replace the contents of \texttt{view.jsp} with this code:
\begin{verbatim}
<%@include file="../init.jsp"%>
<%
long guestbookId = Long.valueOf((Long) renderRequest
.getAttribute("guestbookId"));
%>
\end{verbatim}
\item
You've used a lot of new objects in this JSP, so you must declare them
in \texttt{init.jsp}. Replace the contents of \texttt{init.jsp} with
this:
\begin{verbatim}
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet"%>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<%@ taglib uri="http://liferay.com/tld/frontend" prefix="liferay-frontend" %>
<%@ taglib uri="http://liferay.com/tld/security" prefix="liferay-security" %>
<%@ page import="java.util.List" %>
<%@ page import="com.liferay.portal.kernel.util.ParamUtil" %>
<%@ page import="com.liferay.portal.kernel.util.HtmlUtil" %>
<%@ page import="com.liferay.portal.kernel.util.WebKeys" %>
<%@ page import="com.liferay.petra.string.StringPool" %>
<%@ page import="com.liferay.portal.kernel.model.PersistedModel" %>
<%@ page import="com.liferay.portal.kernel.dao.search.SearchEntry" %>
<%@ page import="com.liferay.portal.kernel.dao.search.ResultRow" %>
<%@ page import="com.liferay.docs.guestbook.model.Guestbook" %>
<%@ page import="com.liferay.docs.guestbook.service.GuestbookEntryLocalServiceUtil" %>
<%@ page import="com.liferay.docs.guestbook.service.GuestbookLocalServiceUtil" %>
<%@ page import="com.liferay.docs.guestbook.model.GuestbookEntry" %>
\end{verbatim}
\end{enumerate}
Many of these objects, such as \texttt{HtmlUtil}, \texttt{ParamUtil},
and \texttt{StringPool}, are Liferay helper utilities that enable you
with a single line of code do things like extract parameters, escape
data, or provide \texttt{String}s that otherwise have to be escaped.
Save your work.
\section{Creating an Actions JSP}\label{creating-an-actions-jsp}
Actions can be performed on your entities once they're stored. Users who
enter Guestbook entries may wish to edit them or delete them. Now you'll
provide that functionality.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click on the
\texttt{src/main/resources/META-INF/resources/guestbook} folder and
select \emph{New} → \emph{File}.
\item
Name the file \texttt{entry\_actions.jsp}.
\item
Paste the following code into the file:
\begin{verbatim}
<%@include file="../init.jsp"%>
<%
String mvcPath = ParamUtil.getString(request, "mvcPath");
ResultRow row = (ResultRow)request.getAttribute(WebKeys.SEARCH_CONTAINER_RESULT_ROW);
GuestbookEntry entry = (GuestbookEntry)row.getObject();
%>
\end{verbatim}
\end{enumerate}
You may have noticed this JSP was included in the Search Container rows
in your \texttt{view.jsp}. As the Search Container loops through
Guestbook entries, this JSP generates an Actions button for each of them
containing two functions: a call to your \texttt{addEntry} method (which
both adds and edits) and a call to your \texttt{deleteEntry} method.
Both calls supply the current \texttt{guestbookId} and \texttt{entryId}
parameters so the Action method has everything it needs to call the
service method that does the work.
Awesome! You've now completed the first iteration of your Guestbook
application.
\begin{figure}
\centering
\includegraphics{./images/guestbook-prototype-form.png}
\caption{You have a form to enter information.}
\end{figure}
\begin{figure}
\centering
\includegraphics{./images/guestbook-prototype-container.png}
\caption{Submitted entries are displayed here.}
\end{figure}
Next you'll review what's been done so far, and you'll deploy and test
your application.
\chapter{Fitting it All Together}\label{fitting-it-all-together}
\begin{verbatim}
Building the Web Front-End
Step 11 of 11
\end{verbatim}
You've created a complete data-driven application from the back-end to
the display. It's a great time to review how everything connects
together.
\section{The Back-End}\label{the-back-end}
The first thing you did was generate back-end services for your
application using Liferay's object-relational mapper, Service Builder.
\begin{figure}
\centering
\includegraphics{./images/service-builder-guestbook.png}
\caption{Service Builder makes generating your database entities and
your Java objects a snap.}
\end{figure}
Using a single \texttt{service.xml} file, you generated your object
model, SQL to create your tables, a persistence layer to perform all
CRUD operations on your data, and a service layer for your business
logic. Then you added business logic to your service layer to expose the
persistence layer's functionality according to your business rules.
With all of that ready, it was time to build a front-end client.
\section{The Front-End}\label{the-front-end}
Once you completed your back-end, you decided to develop a web front-end
using Liferay's MVC Portlet framework. You generated a Liferay MVC
Portlet project and used its Model View Controller development paradigm
to implement a user interface for your back-end functionality.
\begin{figure}
\centering
\includegraphics{./images/guestbook-mvc-diagram-1.png}
\caption{The controller directs page flow in an MVC Portlet
application.}
\end{figure}
First, you created a default view in \texttt{view.jsp}. You created a
button there that links to \texttt{edit\_entry.jsp}, which is used for
both adding and editing Guestbook entries (though you haven't
implemented editing yet). Here, you created a form to capture
information from Users adding Guestbook entries. The form's Action URL
directs processing to your controller's portlet action of the same name.
This action extracts the data from the form (in the
\texttt{ActionRequest} object) and passes it to the business logic you
created in your service layer.
After the business logic runs, your controller passes processing back to
\texttt{view.jsp}, where the new Guestbook entry is displayed.
Now that you've built the application and you can see a clear picture of
how it all works, it's time to test it.
\section{Deploying and Testing the
Application}\label{deploying-and-testing-the-application}
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Drag and drop the \texttt{guestbook-api} module onto the server.
\item
Drag and drop the \texttt{guestbook-service} module onto the server.
\item
Look for the STARTED messages from the console.
\item
Go to your Liferay DXP instance at \texttt{localhost:8080} in your
browser to test your updated application.
\item
Like you did before, use your administrative account to remove the
Guestbook from the page and add it again.
\item
Click \emph{Add Entry}.
\item
Enter a \emph{Name}, \emph{Message}, and \emph{Email Address}.
\item
Click \emph{Submit}.
\item
Verify that your entry appears.
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/guestbook-entry-test.png}
\caption{Your first guestbook and entry appears. Nice job!}
\end{figure}
\section{What's Next?}\label{whats-next}
You've created a working web application and deployed it on Liferay DXP.
If you've created web applications before, though, you know that it's
missing some important features: security, front-end validation, and an
interface for administrators to create multiple guestbooks per portlet.
In the next section, you'll begin adding these features.
\chapter{Writing an Administrative
Portlet}\label{writing-an-administrative-portlet}
The Guestbook application lets users add and view guestbook entries. The
application's back-end, however, is much more powerful. It can support
many guestbooks and their associated entries. But at this point, there's
no UI to support these added features. When you create this UI, you must
also make sure that only administrators can add guestbooks.
To accomplish this, you'll create a Guestbook Admin portlet and place it
in Liferay DXP's administrative interface---specifically, within the
Content menu. This way, the Guestbook Admin portlet is accessible only
to Site Administrators, while users use the Guestbook portlet to create
entries.
In short, this is a simple application with a simple interface:
\begin{figure}
\centering
\includegraphics{./images/admin-app-start.png}
\caption{The Guestbook Admin portlet lets administrators manage
Guestbooks.}
\end{figure}
If you get stuck,
\href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/guestbook/05-admin-portlet}{source
code} for this step is provided.
Are you ready to begin?
Let's Go!{}
\chapter{Creating the Classes}\label{creating-the-classes}
\begin{verbatim}
Writing the Guestbook Admin App
Step 1 of 6
\end{verbatim}
Because the Guestbook and Guestbook Admin applications should be bundled
together, you'll create the new application inside the
\texttt{guestbook-web} project. If you disagree with this design
decision, you can create a separate project for Guestbook Admin; the
project template you'd use is \emph{panel-app}. For now, however, it's
better to go through the process manually to learn how it all works:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Right-click the \texttt{com.liferay.docs.guestbook.portlet} package in
the \texttt{guestbook-web} project and select \emph{New} →
\emph{Class}.
\item
Name the class \texttt{GuestbookAdminPortlet}.
\item
Click \emph{Browse} next to the Superclass and search for
\texttt{MVCPortlet}. Click it and select \emph{OK}.
\item
Click \emph{Finish}.
\end{enumerate}
You now have your Guestbook Admin application's portlet class. For an
administrative application, however, you need at least one more
component.
\section{Panels and Categories}\label{panels-and-categories}
As described in the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu}{product
menu tutorial}, there are three sections of the product menu as
illustrated below.
\begin{figure}
\centering
\includegraphics{./images/product-menu-parts.png}
\caption{The product menu is split into three sections: the Control
Panel, the User menu, and the Sites menu.}
\end{figure}
Each section is called a \emph{panel category}. A panel category can
hold various menu items called \emph{panel apps}. In the illustration
above, the Sites menu is open to reveal its panel apps and categories
(yes, you can nest them).
The most natural place for the Guestbook Admin portlet is in the
\emph{Content \& Data} panel category with Liferay DXP's other
content-based apps. This integrates it nicely in the spot where Site
administrators expect it to be. This also means you don't have to create
a new category for it: you can just create the panel entry, which is
what you'll do next. If you'd like to learn more about panel categories
and apps after this, see the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu}{product
menu tutorial} and the
\href{/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu}{control
menu tutorial}.
Follow these steps to create the panel entry for the Guestbook Admin
portlet:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the dependency you need to extend Liferay DXP's panel categories
and apps. To do this, open \texttt{guestbook-web}'s
\texttt{build.gradle} file and add these dependencies:
\begin{verbatim}
compileOnly group: "com.liferay", name: "com.liferay.application.list.api"
compileOnly group: "com.liferay", name: "com.liferay.petra.string"
\end{verbatim}
\item
After saving the file, right-click \texttt{guestbook-web} and select
\emph{Gradle} → \emph{Refresh Gradle Project}.
\item
Right-click \texttt{src/main/java} in the \texttt{guestbook-web}
project and select \emph{New} → \emph{Package}. Name the package
\texttt{com.liferay.docs.guestbook.application.list} and click
\emph{Finish}.
\item
Right-click your new package and select \emph{New} → \emph{Class}.
Name the class \texttt{GuestbookAdminPanelApp}.
\item
Click \emph{Browse} next to Superclass, search for
\texttt{BasePanelApp}, select it, and click \emph{OK}. Then click
\emph{Finish}.
\end{enumerate}
Great! You've created the classes you need, and you're ready to begin
working on them.
\chapter{Adding Metadata}\label{adding-metadata}
\begin{verbatim}
Writing the Guestbook Admin App
Step 2 of 6
\end{verbatim}
Now that you've generated the classes, you must turn them into OSGi
components. Remember that because components are container-managed
objects, you must provide metadata that tells Liferay DXP's OSGi
container how to manage their life cycles.
Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following portlet key to the \texttt{GuestbookPortletKeys}
class (it's in the \texttt{com.liferay.docs.guestbook.constants}
package):
\begin{verbatim}
public static final String GUESTBOOK_ADMIN =
"com_liferay_docs_guestbook_portlet_GuestbookAdminPortlet";
\end{verbatim}
Save the file.
\item
Open the \texttt{GuestbookAdminPortlet} class and add the
\texttt{@Component} annotation immediately above the class
declaration:
\begin{verbatim}
@Component(
immediate = true,
property = {
"com.liferay.portlet.display-category=category.hidden",
"com.liferay.portlet.scopeable=true",
"javax.portlet.display-name=Guestbooks",
"javax.portlet.expiration-cache=0",
"javax.portlet.init-param.portlet-title-based-navigation=true",
"javax.portlet.init-param.template-path=/",
"javax.portlet.init-param.view-template=/guestbook_admin/view.jsp",
"javax.portlet.name=" + GuestbookPortletKeys.GUESTBOOK_ADMIN,
"javax.portlet.resource-bundle=content.Language",
"javax.portlet.security-role-ref=administrator",
"javax.portlet.supports.mime-type=text/html",
"com.liferay.portlet.add-default-resource=true"
},
service = Portlet.class
)
\end{verbatim}
\item
Hit {[}CTRL{]}+{[}SHIFT{]}+O to add the \texttt{javax.portlet.Portlet}
and other imports.
\end{enumerate}
There are only a few new things here. Note the value of the
\texttt{javax.portlet.display-name} property: \texttt{Guestbooks}. This
is the name that appears in the Site menu. Also note the value of the
\texttt{javax.portlet.name} property:
\texttt{+\ GuestbookPortletKeys.GUESTBOOK\_ADMIN}. This specifies the
portlet's title via the \texttt{GUESTBOOK\_ADMIN} portlet key that you
just created.
Pay special attention to the following metadata property:
\begin{verbatim}
com.liferay.portlet.display-category=category.hidden
\end{verbatim}
This is the same property you used before with the Guestbook portlet.
You placed that portlet in the Social category. The value
\texttt{category.hidden} specifies a special category that doesn't
appear anywhere. The Guestbook Admin portlet goes here because you don't
want users adding it to a page. This prevents them from doing that.
Next, you can configure the Panel app class. Follow these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open the \texttt{GuestbookAdminPanelApp} class and add the
\texttt{@Component} annotation immediately above the class
declaration:
\begin{verbatim}
@Component(
immediate = true,
property = {
"panel.app.order:Integer=300",
"panel.category.key=" + PanelCategoryKeys.SITE_ADMINISTRATION_CONTENT
},
service = PanelApp.class
)
\end{verbatim}
The \texttt{panel.category.key} metadata property determines where to
place the Guestbook Admin portlet in the Product Menu. Remember that
the Product Menu is divided into three main sections: the Control
Panel, the User Menu, and the Site Administration area. The value of
the \texttt{panel.category.key} property is
\texttt{PanelCategoryKeys.SITE\_ADMINISTRATION\_CONTENT}, which means
Guestbook Admin is in \emph{Site Builder} → \emph{Content \& Data}.
The key is provided by
\href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/application-list/application-list-api/src/main/java/com/liferay/application/list/constants/PanelCategoryKeys.java}{the
\texttt{PanelCategoryKeys} class}. The \texttt{panel.app.order} value
determines the rank for the Guestbook Admin portlet in the list.
\item
Finally, update the class to use the proper name and portlet keys:
\begin{verbatim}
public class GuestbookAdminPanelApp extends BasePanelApp {
@Override
public String getPortletId() {
return GuestbookPortletKeys.GUESTBOOK_ADMIN;
}
@Override
@Reference(
target = "(javax.portlet.name=" + GuestbookPortletKeys.GUESTBOOK_ADMIN + ")",
unbind = "-"
)
public void setPortlet(Portlet portlet) {
super.setPortlet(portlet);
}
}
\end{verbatim}
\item
Hit {[}CTRL{]}+{[}SHIFT{]}+O to organize imports. This time, import
\texttt{com.liferay.portal.kernel.model.Portlet} instead of
\texttt{javax.portlet.Portlet}.
\end{enumerate}
Now that the configuration is out of the way, you're free to implement
the app's functionality: adding, editing, and deleting Guestbooks.
That's the next step.
\chapter{Updating Your Service Layer}\label{updating-your-service-layer}
\begin{verbatim}
Writing the Guestbook Admin App
Step 3 of 6
\end{verbatim}
Earlier, you wrote an \texttt{addGuestbook} service method in
\texttt{GuestbookLocalServiceImpl} and only used it to add a default
guestbook. To have full functionality over guestbooks, you must also add
methods for updating and deleting guestbooks, as well as for returning
the number of guestbooks in a Site.
\section{Adding Guestbook Service
Methods}\label{adding-guestbook-service-methods}
Remember that when working with Service Builder, you define your service
in the \texttt{*Impl} classes. After you add, remove, or change the
signature of a method in an \texttt{*Impl} class, you must run Service
Builder. Service Builder updates the affected interfaces and any other
generated code.
Follow these steps to add the required guestbook service methods:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Go to the \texttt{guestbook-service} project and open
\texttt{GuestbookLocalServiceImpl.java} in the
\texttt{com.liferay.docs.guestbook.service.impl} package. Add the
following method for updating a guestbook:
\begin{verbatim}
public Guestbook updateGuestbook(long userId, long guestbookId,
String name, ServiceContext serviceContext) throws PortalException,
SystemException {
Date now = new Date();
validate(name);
Guestbook guestbook = getGuestbook(guestbookId);
User user = userLocalService.getUser(userId);
guestbook.setUserId(userId);
guestbook.setUserName(user.getFullName());
guestbook.setModifiedDate(serviceContext.getModifiedDate(now));
guestbook.setName(name);
guestbook.setExpandoBridgeAttributes(serviceContext);
guestbookPersistence.update(guestbook);
return guestbook;
}
\end{verbatim}
The \texttt{updateGuestbook} method retrieves the \texttt{Guestbook}
by its ID, replaces its data with what the user entered, and then
calls the persistence layer to save it back to the database.
\item
Next, add the following method for deleting a guestbook:
\begin{verbatim}
public Guestbook deleteGuestbook(long guestbookId,
ServiceContext serviceContext) throws PortalException,
SystemException {
Guestbook guestbook = getGuestbook(guestbookId);
List entries = _guestbookEntryLocalService.getGuestbookEntries(
serviceContext.getScopeGroupId(), guestbookId);
for (GuestbookEntry entry : entries) {
_guestbookEntryLocalService.deleteGuestbookEntry(entry.getEntryId());
}
guestbook = deleteGuestbook(guestbook);
return guestbook;
}
\end{verbatim}
It's important to consider what should happen if you delete a
guestbook that has existing entries. If you only deleted the
guestbook, the guestbook's orphaned entries would still exist in the
database. Your \texttt{deleteGuestbook} service method makes a service
call to delete a guestbook's entries before deleting that guestbook.
This way, guestbook entries are never orphaned.
\item
Add a reference to the \texttt{GuestbookEntry} local service, so it
can be injected and used by the \texttt{deleteGuestbook} method:
\begin{verbatim}
@Reference
private GuestbookEntryLocalService _guestbookEntryLocalService;
\end{verbatim}
By convention, Liferay adds these to the bottom of the class.
\item
Use {[}CTRL{]}+{[}SHIFT{]}+O to update your imports, choosing
\texttt{com.liferay.portal.kernel.exception.SystemException}, and then
save \texttt{GuestbookLocalServiceImpl.java}.
\item
In the Gradle Tasks pane on the right side in Liferay Dev Studio DXP,
run Service Builder by opening the \texttt{guestbook-service} module
and double-clicking \texttt{buildService}.
\end{enumerate}
\noindent\hrulefill
\textbf{Note:} If you prefer, you can use
\href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} to
run your Gradle tasks. If you have Blade CLI installed, go to the
\texttt{guestbook-service} folder on your CLI and enter the command
\texttt{blade\ gw\ \ buildService}. This runs Service Builder to build
your services outside of Eclipse.
\noindent\hrulefill
Now that you've finished updating the service layer, it's time to work
on the Guestbook Admin portlet itself.
\chapter{Defining Portlet Actions}\label{defining-portlet-actions}
\begin{verbatim}
Writing the Guestbook Admin App
Step 4 of 6
\end{verbatim}
The Guestbook Admin portlet now needs action methods for adding,
updating, and deleting guestbooks. As with the Guestbook portlet, action
methods call the corresponding service methods. Note that since your
services all run in the same container, any application can call the
Guestbook services. This is an advantage of Liferay DXP's OSGi-based
architecture: different applications or modules can call services
published by other modules. If a service is published, it can be used
via \texttt{@Reference}. You'll take advantage of this here in the
Guestbook Admin portlet to consume one of the same services consumed by
the Guestbook portlet (the \texttt{addGuestbook} service).
\section{Adding Three Portlet
Actions}\label{adding-three-portlet-actions}
The Guestbook Admin portlet must let administrators add, update, and
delete \texttt{Guestbook} objects. You'll create portlet actions to meet
these requirements. Open \texttt{GuestbookAdminPortlet.java} and follow
these steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Add the following action method and instance variables needed for
adding a new guestbook:
\begin{verbatim}
public void addGuestbook(ActionRequest request, ActionResponse response)
throws PortalException {
ServiceContext serviceContext = ServiceContextFactory.getInstance(
Guestbook.class.getName(), request);
String name = ParamUtil.getString(request, "name");
try {
_guestbookLocalService.addGuestbook(
serviceContext.getUserId(), name, serviceContext);
}
catch (PortalException pe) {
Logger.getLogger(GuestbookAdminPortlet.class.getName()).log(
Level.SEVERE, null, pe);
response.setRenderParameter(
"mvcPath", "/guestbook_admin/edit_guestbook.jsp");
}
}
@Reference
private GuestbookLocalService _guestbookLocalService;
\end{verbatim}
Since \texttt{addGuestbook} is a portlet action method, it takes
\texttt{ActionRequest} and \texttt{ActionResponse} parameters. To make
the service call to add a new guestbook, the guestbook's name must be
retrieved from the request. The \texttt{serviceContext} must also be
retrieved from the request and passed as an argument in the service
call. If an exception is thrown, you should display the Add Guestbook
form and not the default view. That's why you add this line in the
\texttt{catch} block:
\begin{verbatim}
response.setRenderParameter("mvcPath",
"/guestbook_admin/edit_guestbook.jsp");
\end{verbatim}
Later, you'll use this for field validation and to show error messages
to the user. Note that \texttt{/guestbook\_admin/edit\_guestbook.jsp}
doesn't exist yet; you'll create it in the next step.
\item
Add the following action method for updating an existing guestbook:
\begin{verbatim}
public void updateGuestbook(ActionRequest request, ActionResponse response)
throws PortalException {
ServiceContext serviceContext = ServiceContextFactory.getInstance(
Guestbook.class.getName(), request);
String name = ParamUtil.getString(request, "name");
long guestbookId = ParamUtil.getLong(request, "guestbookId");
try {
_guestbookLocalService.updateGuestbook(
serviceContext.getUserId(), guestbookId, name, serviceContext);
} catch (PortalException pe) {
Logger.getLogger(GuestbookAdminPortlet.class.getName()).log(
Level.SEVERE, null, pe);
response.setRenderParameter(
"mvcPath", "/guestbook_admin/edit_guestbook.jsp");
}
}
\end{verbatim}
This method retrieves the guestbook name, ID, and the
\texttt{serviceContext} from the request. The \texttt{updateGuestbook}
service call uses the guestbook's ID to identify the guestbook to
update. If there's a problem with the service call, the Guestbook
Admin portlet displays the Edit Guestbook form again so that the user
can edit the form and resubmit:
\begin{verbatim}
response.setRenderParameter("mvcPath",
"/guestbook_admin/edit_guestbook.jsp");
\end{verbatim}
Note that the Edit Guestbook form uses the same JSP as the Add
Guestbook form to avoid duplication of code.
\item
Add the following action method for deleting a guestbook:
\begin{verbatim}
public void deleteGuestbook(ActionRequest request, ActionResponse response)
throws PortalException {
ServiceContext serviceContext = ServiceContextFactory.getInstance(
Guestbook.class.getName(), request);
long guestbookId = ParamUtil.getLong(request, "guestbookId");
try {
_guestbookLocalService.deleteGuestbook(guestbookId, serviceContext);
}
catch (PortalException pe) {
Logger.getLogger(GuestbookAdminPortlet.class.getName()).log(
Level.SEVERE, null, pe);
}
}
\end{verbatim}
This method uses the service layer to delete the guestbook by its ID.
Since the \texttt{deleteGuestbook} action is invoked from the
Guestbook Admin portlet's default view, there's no need to set the
\texttt{mvcPath} render parameter to point to a particular JSP if
there was a problem with the \texttt{deleteGuestbook} service call.
\item
Hit {[}CTRL{]}+{[}SHIFT{]}+O to organize imports. Import the logging
classes from \texttt{java.util}. Save the file.
\end{enumerate}
You now have your service methods and portlet action methods in place.
Before you implement the Guestbook Admin portlet's user interface, you
should update the Guestbook portlet so it can show users all the
Guestbooks your administrators add.
\chapter{Adding Tabs to the Guestbook
Portlet}\label{adding-tabs-to-the-guestbook-portlet}
\begin{verbatim}
Writing the Guestbook Admin App
Step 5 of 6
\end{verbatim}
Before you finish the Guestbook Admin portlet, you must prepare the
Guestbook portlet's UI to display multiple Guestbooks. As administrators
add Guestbooks using the Guestbook Admin portlet, users must be able to
choose which Guestbook they want to sign. They'll do this using a series
of tabs across the top:
\begin{figure}
\centering
\includegraphics{./images/guestbook-tabs.png}
\caption{Users can click a tab to choose which Guestbook to sign.}
\end{figure}
This is surprisingly easy to do using Liferay's Alloy UI tag library:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open \texttt{view.jsp} from the
\texttt{src/main/resources/META-INF/resources/guestbook} folder.
\item
After the first Java snippet (the one that gets the
\texttt{guestbookId} out of the request), add this code:
\begin{verbatim}
<%
List guestbooks = GuestbookLocalServiceUtil.getGuestbooks(scopeGroupId);
for (int i = 0; i < guestbooks.size(); i++) {
Guestbook curGuestbook = (Guestbook) guestbooks.get(i);
String cssClass = StringPool.BLANK;
if (curGuestbook.getGuestbookId() == guestbookId) {
cssClass = "active";
}
%>
<%
}
%>
\end{verbatim}
\item
Save the file.
\end{enumerate}
This code declares the AUI navigation tabs. Then a code scriptlet gets
all the Guestbooks in this scope and loops through each one. As it
examines them, it checks to see if the one it's examining is the current
Guestbook. If so, a CSS style called \texttt{active} is applied.
After this, a new URL called \texttt{viewPageURL} is created that points
to \texttt{view.jsp} with a \texttt{guestbookId} parameter containing
the current Guestbook in the loop. Finally, an
\texttt{\textless{}aui:nav-item\textgreater{}} tag declares the markup
for the tab, using the CSS class, the URL containing the parameters to
navigate to the new Guestbook, and the name to label it.
The loop continues until all the retrieved Guestbooks have tabs.
Awesome! You've updated the Guestbook portlet so it can display all the
Guestbooks administrators add. Now it's time to provide a UI for your
Guestbook Admin portlet so they can do just that.
\chapter{Creating a User Interface}\label{creating-a-user-interface}
\begin{verbatim}
Writing the Guestbook Admin App
Step 6 of 6
\end{verbatim}
It's time to create the Guestbook Admin portlet's user interface. The
portlet's default view has a button for adding new guestbooks. It must
also display the guestbooks that already exist.
Each guestbook's name appears next to an Actions button. The Actions
button reveals options for editing the guestbook, configuring its
permissions, or deleting it.
\section{Step 1: Creating the Default
View}\label{step-1-creating-the-default-view}
The Guestbook Admin portlet's user interface is made up of three JSPs:
the default view, the Actions button, and the form for adding or editing
a guestbook.
Create the default view first:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In \texttt{src/main/resources/META-INF/resources}, create a folder
called \texttt{guestbook\_admin}, where you'll create your JSPs.
\item
Create a file in this folder called \texttt{view.jsp} and fill it with
this code:
\begin{verbatim}
<%@include file="../init.jsp"%>
" />
\end{verbatim}
\end{enumerate}
First is the \texttt{init.jsp} include to gain access to the imports.
Next is a button row with a single button for adding new guestbooks:
\texttt{\textless{}aui:button-row\ cssClass="guestbook-admin-buttons"\textgreater{}}.
The \texttt{cssClass} attribute specifies a custom CSS class for
additional styling. The
\texttt{\textless{}portlet:renderURL\textgreater{}} tag constructs a URL
that points to the \texttt{edit\_guestbook.jsp}. You haven't created
this JSP yet, but you'll use it for adding a new guestbook and editing
an existing one.
Finally, a Liferay search container displays the list of guestbooks.
Three sub-tags define the search container:
\begin{itemize}
\tightlist
\item
\texttt{\textless{}liferay-ui:search-container-results\textgreater{}}
\item
\texttt{\textless{}liferay-ui:search-container-row\textgreater{}}
\item
\texttt{\textless{}liferay-ui:search-iterator\textgreater{}}
\end{itemize}
The
\texttt{\textless{}liferay-ui:search-container-results\textgreater{}}
tag's \texttt{results} attribute uses a service call to retrieve the
guestbooks in the scope. The \texttt{total} attribute uses another
service call to get a count of guestbooks.
The \texttt{\textless{}liferay-ui:search-container-row\textgreater{}}
tag defines what rows contain. In this case, the \texttt{className}
attribute defines \texttt{com.liferay.docs.guestbook.model.Guestbook}.
The \texttt{modelVar} attribute defines \texttt{guestbook} as the
variable for the currently iterated guestbook. In the search container
row, two columns are defined. The
\texttt{\textless{}liferay-ui:search-container-column-text\ property="name"\ /\textgreater{}}
tag specifies the first column. This tag displays text. Its
\texttt{property="name"} attribute specifies that the text to be
displayed is the current guestbook object's \texttt{name} attribute. The
tag \texttt{\textless{}liferay-ui:search-container-column-jsp}
\texttt{path="/guestbook\_admin/guestbook\_actions.jsp"\ align="right"\ /\textgreater{}}
specifies the second (and last) column. This tag includes another JSP
file within a search container column. Its \texttt{path} attribute
specifies the path to the JSP file that should be displayed:
\texttt{guestbook\_actions.jsp}.
Finally, the
\texttt{\textless{}liferay-ui:search-iterator\ /\textgreater{}} tag
iterates through and displays the list of guestbooks. Using Liferay's
search container makes the Guestbook Admin portlet look like a native
Liferay DXP portlet. It also provides built-in pagination so that your
portlet can automatically display large numbers of guestbooks on one
Site.
\section{Step 2: Creating the Actions
Button}\label{step-2-creating-the-actions-button}
Now create the \texttt{guestbook\_actions.jsp} file that displays the
list of possible actions for each guestbook.
Create a new file called \texttt{guestbook\_actions.jsp} in your
project's \texttt{/guestbook\_admin} folder. Paste in this code:
\begin{verbatim}
<%@include file="../init.jsp"%>
<%
String mvcPath = ParamUtil.getString(request, "mvcPath");
ResultRow row = (ResultRow) request
.getAttribute("SEARCH_CONTAINER_RESULT_ROW");
Guestbook guestbook = (Guestbook) row.getObject();
%>
\end{verbatim}
This JSP comprises the pop-up actions menu that shows the actions users
can perform on a guestbook: editing it or deleting it. First,
\texttt{init.jsp} is included because it contains all the JSP imports.
Because \texttt{guestbook\_actions.jsp} is included for every Search
Container row, it retrieves the guestbook in the current iteration. The
scriptlet grabs that guestbook so its ID can be supplied to the menu
tags.
The \texttt{\textless{}liferay-ui:icon-menu\textgreater{}} tag dominates
\texttt{guestbook\_actions.jsp}. It's a container for menu items, of
which there are currently only two (you'll add more later). The Edit
menu item displays the Edit icon and the message \emph{Edit}:
\begin{verbatim}
\end{verbatim}
The \texttt{editURL} variable comes from the
\texttt{\textless{}portlet:renderURL\ var="editURL"\textgreater{}} tag
with two parameters: \texttt{guestbookId} and \texttt{mvcPath}. The
\texttt{guestbookId} parameter specifies the guestbook to edit (it's the
one from the selected search container result row), and the
\texttt{mvcPath} parameter specifies the Edit Guestbook form's path.
The Delete menu item displays a delete icon and the default message
\emph{Delete}:
\begin{verbatim}
\end{verbatim}
Unlike the \texttt{editURL}, which is a render URL that links to the
\texttt{edit\_guestbook.jsp}, the \texttt{deleteURL} is an action URL
that invokes the portlet's \texttt{deleteGuestbook} action. The tag
\texttt{\textless{}portlet:actionURL\ name="deleteGuestbook"\ var="deleteURL"\textgreater{}}
creates this action URL, which only takes one parameter: the
\texttt{guestbookId} of the guestbook to be deleted.
\section{Step 3: Creating the Edit Guestbook
JSP}\label{step-3-creating-the-edit-guestbook-jsp}
Now there's just one more JSP file left to create: the
\texttt{edit\_guestbook.jsp} that contains the form for adding a new
guestbook and editing an existing one.
Create a new file called \texttt{edit\_guestbook.jsp} in your project's
\texttt{/guestbook\_admin} directory. Then add the following code to it:
\begin{verbatim}
<%@include file = "../init.jsp" %>
<%
long guestbookId = ParamUtil.getLong(request, "guestbookId");
Guestbook guestbook = null;
if (guestbookId > 0) {
guestbook = GuestbookLocalServiceUtil.getGuestbook(guestbookId);
}
%>
\end{verbatim}
After the \texttt{init.jsp} import, you declare a \texttt{null}
guestbook variable. If there's a \texttt{guestbookId} parameter in the
request, you use the \texttt{guestbookId} to retrieve the corresponding
guestbook via a service call for edit. Otherwise, you know that you're
adding a new guestbook.
Next is a view URL that points to the Guestbook Admin portlet's default
view. This URL is invoked if the user clicks \emph{Cancel} on the Add
Guestbook or Edit Guestbook form. After that, you create an action URL
that invokes either the Guestbook Admin portlet's \texttt{addGuestbook}
method or its \texttt{updateGuestbook} method, depending on whether the
\texttt{guestbook} variable is null.
If a guestbook is being edited, its name should appear in the form's
name field. You use the following tag to define a model of the guestbook
that can be used in the AlloyUI form:
\begin{verbatim}
\end{verbatim}
The form is created with the following tag:
\begin{verbatim}
\end{verbatim}
The form is submitted via the \texttt{editGuestbookURL}, which calls the
Guestbook Admin portlet's \texttt{addGuestbook} or
\texttt{updateGuestbook} action method, as discussed above.
The \texttt{guestbookId} must appear on the form so that it can be
submitted. The user, however, doesn't need to see it. Thus, you specify
\texttt{type="hidden"}:
\begin{verbatim}
\end{verbatim}
The name, of course, should be editable by the user so it's not hidden.
The last item on the form is a button row with two buttons. The
\emph{Submit} button submits the form, invoking the
\texttt{editGuestbookURL} which, in turn, invokes either the
\texttt{addGuestbook} or \texttt{updateGuestbook} method. The
\emph{Cancel} button invokes the \texttt{viewURL} which displays the
default view.
Excellent! You've now finished creating the UI for the Guestbook Admin
portlet. It should now match the figure below:
\begin{figure}
\centering
\includegraphics{./images/admin-app-start.png}
\caption{The Guestbook Admin portlet can add or edit guestbooks,
configure their permissions, or delete them.}
\end{figure}
Save all your files and wait for redeploy. Test out the Guestbook Admin
portlet! Try adding, editing, and deleting guestbooks.
\noindent\hrulefill
\textbf{Note:} If you get ``Guestbook is unavailable'' errors, remove
the modules from the server, redeploy them, and test again.
\noindent\hrulefill
Now all the Guestbook application's primary functions work. There are
still many missing features, however. For example, if there's ever an
error, users never see it: all the code written so far just prints
messages in the logs. Next, you'll learn how to display those errors to
the user.
\chapter{Displaying Messages and
Errors}\label{displaying-messages-and-errors}
When users interact with your application, they perform tasks it
defines, like saving or editing things. The Guestbook application is no
different. Your application should provide feedback on these operations
so users can know they completed. Up to now, you've been placing this
information in logs that only administrators can access. Wouldn't it be
better to show users these messages?
\begin{figure}
\centering
\includegraphics{./images/guestbook-status-message.png}
\caption{You can use Liferay's APIs to display helpful messages.}
\end{figure}
That's exactly what you'll do next, in three steps:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
Create language keys for your messages.
\item
Add the error messages to your action methods.
\item
Report those error messages in your JSPs.
\end{enumerate}
If you get stuck,
\href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/guestbook/06-messages/com-liferay-docs-guestbook}{source
code} for this step is provided.
Ready to get started?
Let's Go!{}
\chapter{Creating Language Keys}\label{creating-language-keys}
\begin{verbatim}
Displaying Messages and Errors
Step 1 of 3
\end{verbatim}
Modern applications should place messages and form field labels in a
language keys files that hold multiple language translations. Here,
you'll learn how to provide a \emph{default} set of English language
keys for your application. For more information on language keys and
providing automatically translated language keys,
\href{/docs/7-2/frameworks/-/knowledge_base/f/automatically-generating-translations}{see
Generating Translations}.
Language keys are stored in the \texttt{Language.properties} file
included in your \texttt{guestbook-web} module.
\texttt{Language.properties} is the default, but you can create more
translations by appending the ISO-639 language code to the file name
(e.g., \texttt{Language\_es.properties} for Spanish or
\texttt{Language\_de.properties} for German). For now, stick to the
default language keys.
Follow these steps to create your language keys:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open \texttt{/src/main/resources/content/Language.properties} in your
\texttt{guestbook-web} module. Remove the default keys in this file.
\item
Paste in the following keys:
\begin{verbatim}
entry-added=Entry added successfully.
entry-deleted=Entry deleted successfully.
guestbook-added=Guestbook added successfully.
guestbook-updated=Guestbook updated successfully.
guestbook-deleted=Guestbook deleted successfully.
\end{verbatim}
\item
Save the file.
\end{enumerate}
Your messages are now in place, and your application can use them. Next,
you'll add them to your action methods.
\chapter{Adding Failure and Success
Messages}\label{adding-failure-and-success-messages}
\begin{verbatim}
Displaying Messages and Errors
Step 2 of 3
\end{verbatim}
To display feedback to users properly, you must edit your portlet
classes to use Liferay DXP's \texttt{SessionMessages} and
\texttt{SessionErrors} classes. These classes collect messages that the
view layer shows to the user through a tag.
You'll add these messages to code that runs when the user triggers a
system function that can succeed or fail, such as creating, editing, or
deleting a Guestbook or Guestbook entry. This happens in action methods.
You must update these methods to handle failure and success states in
\texttt{GuestbookPortlet.java} and \texttt{GuestbookAdminPortlet.java}.
Start by updating \texttt{addEntry} and \texttt{deleteEntry} in
\texttt{GuestbookPortlet.java}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Find the \texttt{addEntry} method in \texttt{GuestbookPortlet.java}.
In the first \texttt{try...catch} block's \texttt{try} section, and
add the success message just before the closing \texttt{\}}:
\begin{verbatim}
SessionMessages.add(request, "entryAdded");
\end{verbatim}
This uses Liferay's \texttt{SessionMessages} API to add a success
message whenever a Guestbook is successfully added. It looks up the
message you placed in the \texttt{Language.properties} file and
inserts the message for the key \texttt{entry-added} (it automatically
converts the key from camel case).
\item
Below that, in the \texttt{catch} block, find the following code:
\begin{verbatim}
System.out.println(e);
\end{verbatim}
\item
Beneath it, paste this line:
\begin{verbatim}
SessionErrors.add(request, e.getClass().getName());
\end{verbatim}
Now you not only log the message to the console, you also use the
\texttt{SessionErrors} object to show the message to the user.
\end{enumerate}
Next, do the same for the \texttt{deleteEntry} method:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
After the logic to delete the entry, add a success message:
\begin{verbatim}
SessionMessages.add(request, "entryDeleted");
\end{verbatim}
\item
Find the \texttt{Logger...} block of code in the \texttt{deleteEntry}
method and after it, paste this line:
\begin{verbatim}
SessionErrors.add(request, e.getClass().getName());
\end{verbatim}
\item
Hit {[}CTRL{]}+{[}SHIFT{]}+O to import
\texttt{com.liferay.portal.kernel.servlet.SessionErrors} and
\texttt{com.liferay.portal.kernel.servlet.SessionMessages}. Save the
file.
\end{enumerate}
Well done! You've added the messages to \texttt{GuestbookPortlet}. Now
you must update \texttt{GuestbookAdminPortlet.java}:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Open \texttt{GuestbookAdminPortlet.java} and look for the same cues.
\item
Add the appropriate success messages to the \texttt{try} section of
the \texttt{try...catch} in \texttt{addGuestbook},
\texttt{updateGuestbook}, and \texttt{deleteGuestbook}, respectively:
\begin{verbatim}
SessionMessages.add(request, "guestbookAdded");
SessionMessages.add(request, "guestbookUpdated");
SessionMessages.add(request, "guestbookDeleted");
\end{verbatim}
\item
In the \texttt{catch} section of those same methods, find
\texttt{Logger.getlogger...} and paste the \texttt{SessionErrors}
block beneath it:
\begin{verbatim}
SessionErrors.add(request, pe.getClass().getName());
\end{verbatim}
\item
Hit {[}CTRL{]}+{[}SHIFT{]}+O to import \texttt{SessionErrors} and
\texttt{SessionMessages}. Save the file.
\end{enumerate}
Great! The controller now makes relevant and detailed feedback
available. Now all you need to do is publish this feedback in the view
layer.
\chapter{Adding Messages to JSPs}\label{adding-messages-to-jsps}
\begin{verbatim}
Displaying Messages and Errors
Step 3 of 3
\end{verbatim}
Any messages the user should see are now stored in either
\texttt{SessionMessages} or \texttt{SessionErrors}. Next, you'll make
these messages appear in your JSPs.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the \texttt{guestbook-web} module, open
\texttt{guestbook/view.jsp}. Add the following block of success
messages to the top of the file, just below the \texttt{init.jsp}
include statement:
\begin{verbatim}
\end{verbatim}
This tag accesses what's stored in \texttt{SessionMessages}. It has
two attributes. The first is the \texttt{SessionMessages} key that you
provided in the \texttt{GuestbookPortlet.java} class's add and delete
methods. The second looks up the specified key in the
\texttt{Language.properties} file. You could have specified a
hard-coded message here, but it's far better to provide a localized
key.
\item
Now open \texttt{guestbook\_admin/view.jsp}. Add the following block
of success messages in the same spot below the include:
\begin{verbatim}
\end{verbatim}
\end{enumerate}
\begin{figure}
\centering
\includegraphics{./images/message-complete.png}
\caption{Now the message displays the value you specified in
\texttt{Language.properties}.}
\end{figure}
Congratulations! You've added useful feedback for operations in your
application.
Your application is shaping up, but it is missing another important
feature: permissions. Next, you'll add permission checking for your
guestbooks and entries.
Look for the next part soon!
\chapter{Using Resources and
Permissions}\label{using-resources-and-permissions}
Your application is a great foundation to build on. What comes next?
What if users want a Guestbook that's limited to certain trusted people?
What if you don't want just any old user to go around editing or
deleting people's Guestbook entries? To do that, you have to implement
permissions.
Thankfully, with Liferay DXP you don't have to write an entire
permissions system from scratch: the framework provides a robust and
well-tested permissions system that you can implement quickly. You'll
follow Liferay's well-defined process for implementing permissions,
called \emph{DRAC}:
\begin{itemize}
\tightlist
\item
\textbf{Define} all resources and permissions
\item
\textbf{Register} all defined resources in the permissions system
\item
\textbf{Associate} permissions with resources
\item
\textbf{Check} for permission before returning resources
\end{itemize}
If you get stuck,
\href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/guestbook/07-permissions/com-liferay-docs-guestbook}{source
code} for this step is provided.
Ready to start?
Let's Go!{}
\chapter{Defining Permissions}\label{defining-permissions}
\begin{verbatim}
Implementing Permissions
Step 1 of 5
\end{verbatim}
Liferay DXP's permissions framework is configured declaratively, like
Service Builder. You define all your permissions in an XML file that by
convention is called \texttt{default.xml} (but you could really call it
whatever you want). Then you implement permissions checks in the
following places in your code:
\begin{itemize}
\tightlist
\item
In the view layer, when showing links or buttons to protected
functionality
\item
In the actions, before performing a protected action
\item
Later, in your service, before calling the remote service
\end{itemize}
You should first define the permissions you want. To get started, think
of your application's use cases and how access to that functionality
should be controlled:
\begin{itemize}
\item
The Add Guestbook button should be available only to administrators.
\item
The Guestbook tabs should be filtered by permissions so administrators
can control who can see them.
\item
To prevent anonymous users from spamming the guestbook, the Add Entry
button should be available only to Site members.
\item
Users should be able to set permissions on their own entries.
\end{itemize}
Now you're ready to create the permissions configuration. Objects in
your application (such as \texttt{Guestbook} and
\texttt{GuestbookEntry}) are defined as \emph{resources}, and
\emph{resource actions} manage how users can interact with those
resources. There are therefore two kinds of permissions: portlet
permissions and resource (or model) permissions. Portlet permissions
protect access to global functions, such as \emph{Add Entry}. If users
don't have permission to access that global function, they're missing a
portlet permission. Resource permissions protect access to objects, such
as \texttt{Guestbook} and \texttt{GuestbookEntry}. A user may have
permission to view one \texttt{GuestbookEntry}, view and edit another
\texttt{GuestbookEntry}, and may not be able to access another
\texttt{GuestbookEntry} at all. This is due to a resource permission.
\begin{figure}
\centering
\includegraphics{./images/permission-types.png}
\caption{Portlet permissions and resource permissions cover different
parts of the application.}
\end{figure}
\section{Defining Model Permissions}\label{defining-model-permissions}
First, create the permissions file in the \texttt{guestbook-service}
project:
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
In the \texttt{/src/main/resources/META-INF} folder, create a
subfolder called \texttt{resource-actions}.
\item
Create a new file in this folder called \texttt{default.xml}.
\item
Click the \emph{Source} tab. Add the following \texttt{DOCTYPE}
declaration to the top of the file:
\begin{verbatim}
\end{verbatim}
\item
Place the following wrapper tags into your \texttt{default.xml} file,
below the \texttt{DOCTYPE} declaration:
\begin{verbatim}
\end{verbatim}
You'll define your resource and model permissions inside these tags.
\item
Next, place the permissions for your
\texttt{com.liferay.docs.guestbook} package between the
\texttt{\textless{}resource-action-mapping\textgreater{}} tags:
\begin{verbatim}
com.liferay.docs.guestbook
com_liferay_docs_guestbook_portlet_GuestbookPortlet
com_liferay_docs_guestbook_portlet_GuestbookAdminPortlet
true